| paradise | ||
| paradise-phone | ||
| .gitattributes | ||
| LICENSE | ||
| LICENSE-lucide.txt | ||
| README.md | ||
paradise-skins
Skins for paradise, written in the paradise skin language. Each folder here is one skin: a skin.pss, its variants in variants/, and whatever files they name.
paradise- the desktop skin, built into paradise, so there is nothing to install. See Built in below.paradise-phone- the same for a phone, built into paradise too, and the one a phone starts on. Dev mode shows it on the desktop, run as a phone.
Installing
paradise's build copies every folder here but the built-in ones into skins/ beside the exe, folders inside a skin and all, and install takes them along. Only what git has goes. This repo has to be checked out beside paradise (../paradise-skins, the same way as ../xo-app) or paradise won't build.
Your own skins go in the user data folder:
- Windows:
%APPDATA%\paradise\skins\<name>\skin.pss - Linux:
${XDG_DATA_HOME:-~/.local/share}/paradise/skins/<name>/skin.pss
On Android the skins ship inside the APK, under assets/skins.
Dev builds run as paradise-dev, so their folder is paradise-dev instead. A skin in the user folder wins over one beside the exe with the same folder name, and either wins over a built-in one. A folder whose name starts with . is left out. So is one with a space at either end or a control character in its name, and the log says so. A skin.pss that doesn't read, or names no layout paradise has, is left out with why in the log. 128 skins and variants are found at most, all told.
Pick one from Theme on the settings page. It switches straight away and paradise remembers it. A skin that fails to load says why in a message box and in the log, and the one you had stays up. On a phone only skins for the phone layout are offered. On the desktop the desktop's are, and with Dev mode on the phone's too.
Working on a Skin
- Dev mode, on the settings page or
--dev, loads the skin again whenever a file in its folder changes, in any folder inside it down to 4 deep, or its variant's file where that's kept elsewhere. One that won't load leaves the last one up with what's wrong across the top of the window, asskin.pss line N: WHATorits variant line N: WHAT, until the next save that does. paradise remembers whether it's on. A built-in skin has no folder to watch: work on a copy, see Built in. skin-check [--skins DIR]... PATH..., built beside paradise, loads skins without opening anything and printsPATH:LINE: error: WHATfor each mistake, orPATH: error: WHATwhen there's no line to point at. What paradise works around but you should hear about is the same withwarning:. A skin's folder or itsskin.psschecks it and every variant in itsvariants/folder, each variant saying only the warnings it adds; a variant's file,variants/NAME.pss, checks just that one over its skin. An add-on's.pxt, or the folder holding it, checks it over every skin in each--skins DIR, or over the two built in without one, and says which skin a mistake was over. It exits 1 when there's a mistake. It needs a display, so headless it runs underxvfb-desktop.- To work on this repo's skins in place, link the user data folder's
skinsto this checkout instead of copying. On Windows,mklink /J "%APPDATA%\paradise-dev\skins" D:\path\to\paradise-skins. - paradise's
tools/vscode-skinis a VS Code extension for.pssand.pxt: highlighting, these docs on hover, completion, and skin-check's mistakes and warnings on save. Its README says how to install it.
To try a phone's skin on the desktop, in the phone's own pixels:
paradise --dev --phone 540x1200 --insets 0,48,0,32
--phone WxH is the phone's size in physical pixels and --insets L,T,R,B what its bars and cutout cover. Ctrl+R turns it, the bars going round with it; without --phone it swaps the window's width and height. --hidden starts paradise with nothing up; launching it again shows it.
Warnings
- The language will change while paradise does. This paradise reads language 1, which a skin can say with
language: 1;in@skin. A skin in the online catalog that needs a newer one isn't offered. - Sizes are whole points for now. A fraction is rounded, and skin-check warns of one written as a size.
text-widthin a bracket is always 0 for now, and skin-check warns of it.- Shuffle and then repeat, each while it's on, are the
modes, which a skin places where it likes: the phone's sit over the right end of the song's byline, the artist and album line, their middle on the middle of that line whatever the type's size (paradise-phone'smodesisanchor: byline right center, so they're no part of the map and the title has the song's whole width, and they go with the byline off a page with no room for it; the byline hasavoid: modes, to stop short of them by their left margin, and has its room back with neither on), and the desktop's sit over the right end of the seek bar. Ago: queueon them opens the queue, where their keys are. Both shipped skins draw them in$text, the page's ink, and the haze is calm under each as under a key's icon. The status bar has the add-ons' marks alone. - The settings, plugins, plugin, licences and filter pages are paradise's own, the same under every skin, and so is the top row over them on the desktop. A rule for one is a mistake. They're light or dark as the settings page says: Theme for paradise's own themes, Settings theme for any other, the system's until it's changed.
skin.pss - The Shape of It
@skin {
name: "My skin";
layout: paradise-phone;
kind: smooth;
transition: staggered-slide;
}
@font selawik-light "fonts/selawik-light.ttf";
$head: selawik-light 25;
$bg: #f7f8f9;
$text: #2b2d31;
$green: #2fc06a;
$blue: #2f8fe8;
$accent: linear(right, $green, $blue);
@dark {
$bg: #16181c;
$text: #eceef1;
}
// The queue: the header, then the list.
queue {
background: $bg;
areas:
"header" 68
"list" fill;
}
row:selected {
background: $text 10%;
}
| file | |
|---|---|
skin.pss |
The skin. |
variants/<id>.pss |
A variant: the same language, read after the skin as if written below it. See Variants. |
A file is UTF-8, and one that isn't is a mistake. A byte order mark at the start is skipped. Outside strings and comments it's ASCII.
//comments run to the end of the line./* */comments can span lines.- Whitespace mostly doesn't matter:
(width - 8)and(width -8)are both fine. It does inside a selector's step (row:selected,icon.album,row::before) and a dotted name (cover.width), which can't have spaces in them. - Names are letters, digits,
_and-, starting with a letter or_. A-touching a name is part of it, so(width-8)reads a namewidth-8, which is 0, and skin-check warns of it. Put spaces round a minus after a name. - Strings are in double quotes, on one line.
\"and\\are the only escapes. - A declaration is
NAME: VALUES;. The;can be left off the last one in a block.
skin.pss - @skin
Every skin has one @skin block, and each name in it is said once.
@skin {
name: "paradise phone";
layout: paradise-phone;
kind: smooth;
transition: staggered-slide;
cover-pixels: 1024;
language: 1;
}
in @skin |
|
|---|---|
name: "..." |
Shown under Theme on the settings page. Defaults to the folder name. |
layout: paradise-phone|paradise-desktop |
Required. The app's layout the skin is for: its pages and their parts, see Pages and Parts. A phone loads only paradise-phone skins. |
kind: smooth|pixel N |
smooth, or pixel N where N is how big a skin pixel is in points. Without it the skin is smooth, and skin-check warns. See Pixel Skins. |
transition: staggered-slide|slide|fade|none |
How a change of page is shown. None or none changes pages at once. |
transition { PART: DELAY LENGTH; ... } |
The style's times changed, in milliseconds, whole numbers. PART is background, page, line, title or arrows, each once. |
cover-pixels: N |
How far covers are averaged down before they're made to the size they're drawn at. 8 to 1024, 128 unless it says. |
language: 1 |
The language the skin is written in. Anything but 1 is a mistake. |
staggered-slide slides the page over 450 ms, the header's line 30 behind it over 435 and its title 60 behind over 420, and fades the arrows over 300. slide moves the page, line and title together over 450. fade moves nothing and crosses them. The times of slide and fade are stand-ins. A transition only shows where the GPU draws the window; drawn in software, pages change at once.
skin.pss - Pixel Skins
A pixel N skin is drawn in its own pixels, each a whole number of the screen's: the one nearest N points, .5 going up, and fewer while the window's smallest size, 180 skin pixels a side on the phone's layout, won't fit the screen either way round. Never fewer than one. A phone keeps it when it turns.
- Its numbers are skin pixels, and the window resizes by whole ones.
- Its shapes, its SVGs and the app's icons on its pages are pixel art: drawn at a skin pixel to the pixel with no smoothing, then blown up. Its pictures are blown up in blocks too. A cover is drawn to the screen's pixels unless the skin says
pixelated. - What moves, lists, sheets and pages, moves by whole skin pixels.
- A
1bitfont is drawn as pixel art. A smooth font is drawn at the screen's resolution, so a pixel skin brings a1bitfont of its own. - The settings, plugins and plugin pages, and the menus, are in the screen's own points over it, as they are over a smooth skin.
skin.pss - Fonts
@font selawik-light "fonts/selawik-light.ttf";
@font fusion "fonts/fusion-pixel-12px.otf" 1bit;
@font NAME "file" names a TrueType or OpenType font, 1bit after it for a pixel font. It's sized where it's used, font: selawik-light 25, 1 to 256. Of two with one name the later is the one.
The file is looked for in the skin's folder, from the variant's folder first for a variant's @font. fonts/selawik-light.ttf and fonts/selawik-semilight.ttf are built into paradise and found by those names when the folders don't have them. A font that isn't there is a warning, and what uses it gets the standard look's font.
A smooth font is drawn at the screen's own resolution. What it lacks, CJK, Greek, Cyrillic and Vietnamese among it, comes from xo-app's fallback fonts at its size. A 1bit font draws only its own glyphs.
skin.pss - Tokens
Tokens are named values any part can read, written with a $, declared and read the same way. They're set at the top of the file, or in a rule for that part and what's inside it.
$green: #2fc06a;
$blue: #2f8fe8;
$accent: linear(right, $green, $blue);
$head: selawik-light 25;
$pad: 20;
now-playing tabs {
$dim: $text 80%;
}
@is only for the file's blocks, so a token can be called anything.- A token made of tokens is worked out where it's read: each token in it is found again for the part reading it, by Order below. So
$accentfollows@dark's green and blue, a variant's, and a page's own. 16 deep at most; past that it can't be used. - A token is a colour, a gradient, a size, a bracket or a font. A picture in a token is loaded, so it has to be there, but
iconandbackgrounddon't read one through a token for now: write it where it's used.icon: $nameis ignored, and skin-check warns of it. - A token whose value can't be used where it's read, a size where a colour goes, is a warning, and the part goes without it.
The shared tokens, read by the app's parts and the standard look, each with one meaning. Any the skin doesn't set are the standard look's. A skin can add any tokens of its own.
| token | |
|---|---|
$bg |
A page's background. |
$panel |
A raised surface: an icon's cover, a card. |
$text $soft $dim |
Ink, strong to quiet. $text is the colour of anything nothing else colours. |
$rule |
Lines between rows. |
$track |
The empty part of a seek bar or slider. |
$thumb |
A scrollbar's thumb. |
$select |
A selected row: on the desktop the library's. The queue has no selection. |
$hover |
The ground under the mouse. |
$highlight |
Behind selected text. |
$key $key-down |
A key at rest and pressed. |
$focus |
The ring round what the keys act on. |
$accent |
Where something is happening: a colour or a gradient. |
$accent-fill |
A deeper accent, a colour or a gradient, for fills that carry ink, a word or a glyph. A skin that sets $accent and not this one gets its $accent here. |
$on-accent |
Ink over the accent's fill. While paradise has an accent from the cover that's playing, this is the ink that reads over it, white or dark, whatever the skin says. |
$accent-a $accent-b |
The two colours of the accent gradient, from and to. paradise makes them from the cover that's playing, a pair for the page's own ground and another for a part on the haze, deeper or paler to stand clear of it: the cover's colour, or the heaviest of its colours where it has many and no one of them; with no cover they are the skin's own declaration. A skin that wants them says $accent-a: $green; $accent-b: $blue; for its own and writes its gradients with them: while paradise has a pair they are that, over anything the skin says, and the part's page decides which. A skin that doesn't, keeps its colours. A cover with almost no colour, grey or sepia or a black and white photograph, makes two greys. paradise's own pages follow the cover either way. PARADISE_ACCENT=off in the environment turns it off. |
$warn $error |
Something that needs the user, a sign-in, and something that failed: the ink of its words and its icon. Never the accent. |
$radius |
The usual corner. |
$body $small $tiny $title $head |
Fonts. At the top of the file each is a font token or a @font's name and its size. |
A font token, $small, $tiny, $title or $head, that the skin doesn't set anywhere it can be found is the skin's $body there, so a pixel skin with one font sets one. Found at all, even further out than a $body, it's itself. Set only in @dark, it's $body in the light.
skin.pss - @dark
@dark {
$bg: #16181c;
$text: #eceef1;
now-playing {
haze: saturation 20 value -25 calm -70;
}
}
What changes while the system is dark. It holds tokens and rules, not other blocks, and goes at the top of the file. In the dark its tokens and rules beat the plain ones that say as much, wherever either is written.
skin.pss - Values
- Numbers are points.
%is a share of the room andfra share of what's left. Any other unit is a mistake. A number is at most 1000000000. - Colours are
#rgb,#rrggbbor#rrggbbaa, or a token.$text 80%is the token at that strength: its alpha times 0.8. - Gradients are
linear(right|down|diagonal|DEGREES, A, B[, C, D])andradial(A, B[, C, D]), two to four colours or tokens, spread evenly unless one says where:linear(down, $green 0%, $blue 70%). Degrees are CSS's, 0 up and 90 to the right.radial()is CSS's radial-gradient() of its colours: an ellipse from the middle of what it paints, that shape, reaching its corners. Where a colour is wanted, a text's say, a gradient gives its first. A border takes a gradient too: round a ring it runs round the ring, and a border of straight sides takes its first colour. noneis nothing: no background, no border, no icon.- Pictures are strings,
"art/keys.png"and"icons/play.svg": PNG, BMP or JPEG, or SVG by its name ending in.svg. One a variant names is looked for in the variant's folder first, then the skin's. One that isn't there or won't load is a mistake.
Arithmetic goes in brackets, from CSS's calc(): + - * /, a minus before, and brackets in brackets. Dividing by 0 is 0.
| in a bracket | |
|---|---|
width height top left bottom right |
The room the part is laid out in. A stand-in. |
NAME.width, NAME.height, .top, .left, .bottom, .right |
That part's box as laid out, the first of that name on the page. A page's name is the page: (now-playing.width * 2 / 5). A page is laid out twice, so a part laid out after this one can be read. |
window.width, window.height |
The whole window's size, its bars and all, whatever part reads it and whatever room it has; turned, the other way about. paradise-phone's mini player is max-width: (window.height - 2 * $float) with align: center, so turned it's as wide as upright, in the middle. window is a stand-in name. |
line |
The part's own font's line height. |
$name |
A token that's a size. |
$name.line |
A font token's line height. |
text-width |
The part's own text's width. Always 0 for now. |
A name a bracket doesn't know is 0, and so is a $name in one that the skin never sets. skin-check warns of both.
An SVG is read for what an icon needs: path, rect, circle, ellipse, line, polyline, polygon and g, with fill, stroke, stroke-width, the opacities and fill-rule, as attributes or in style. Colours are #rgb, #rrggbb, #rrggbbaa, rgb(), none, black, white, red, green, blue, yellow, orange, gray, currentColor and var(--name). It needs a viewBox, or a width and a height. Up to 32 shapes of 256 segments. Anything else, a transform or a gradient or text, is refused by name.
currentColor is the part's color, a gradient running across the icon. var(--name) is the skin's token $name where the part is, its first colour: $accent in a .pss is var(--accent) in an SVG. With no such token it's var()'s fallback, or what the element would have had without it.
skin.pss - Pages and Parts
The pages are the app's. On the phone layout: now-playing, queue under now playing, and library. On the desktop layout: now-playing, queue and library, all top pages, the tabs of one window. settings, plugins and a plugin's page are the standard pages, the same under every skin, and aren't styled here.
A page holds parts the app knows, each with a fixed name. Some are needed. A needed part the skin doesn't place comes anyway in the standard look, the header and the desktop's top row in a band across the top and the rest at the foot, and skin-check warns. An optional part the skin doesn't place doesn't show. A part placed but not styled takes the standard look.
Parts on the phone layout, needed parts in bold:
| page | parts |
|---|---|
| every page | header (with label, line, back, forward, close) |
| now-playing | visualizer, cover, statusbar (with indicator, which has label), modes (with indicator), info (with title, byline which has artist and album, extra, summary), time, total, source, seek (with track, buffer, fill, knob), transport (with prev, play, next, add), foot, button, lyrics (with lyric-before, lyric, lyric-after, lyric-progress) |
| lyrics | lyric-lines (with scrollbar), mini-player (with visualizer, cover, title, prev, play, next, add, elapsed) |
| queue | count, list (with row, which has cover, title, artist, album, length; insert; swipe; scrollbar), shuffle, repeat, sleep, lyrics-toggle, mini-player (with visualizer, cover, title, prev, play, next, add, elapsed), toast, button |
| library | views (with tab), search (with edit, which has selection and caret; clear), tree (with row, which has fold, name, edit with selection and caret, badge and select; icon, which has cover, name, subtitle, badge, select; peek, which has item (with pick, play, queue, unhide, cover, number, title, subtitle, length, select) and a peek inside it; scrollbar; scrubber, which has bubble), tracks (with row, which has cover, title, artist, album, length, badge, select; insert; scrollbar; scrubber, which has bubble), empty, mini-player (with visualizer, cover, title, prev, play, next, add, elapsed), selectbar (with count, select-all, play, add-to, more, clear) |
Parts on the desktop layout. The top row, its tabs and caption with the gear, the split with its grip and the tracks beside the tree are settled. The names volume, search, columns and its label, the cells number and other, and count are stand-ins, named for the old skin's, and so is which parts are needed.
| page | parts |
|---|---|
| every page | tabs (with tab), caption (with gear, minimize, maximize, close). No header. |
| now-playing | visualizer, cover, statusbar (with indicator, which has label), modes (with indicator), info (as the phone's), time, source, seek (with track, buffer, fill, knob), transport (with prev, play, next), volume (with track, fill, knob), button |
| queue | list (as the phone's, with no swipe), shuffle, repeat, sleep |
| library | search (with edit, which has selection and caret), split (with grip), views (with tab), tree (as the phone's, with no scrubber), tracks (with columns, which has label; row, which has number, title, artist, album, length, other, badge; insert; scrollbar), count |
- The header is xo-app's. Its pieces take a font and
text-transformfor thelabel, abackgroundfor theline, and acolorand abackgroundfor the arrows and the X. Their sizes are xo-app's. The label is in capitals unless it saystext-transform: lowercase, and where it's longer than the room between the arrows it ends in an ellipsis. - The tabs and the library's views are xo-app's tabs. A
tabtakes acolorand a font, and acolorat:hover. The tab that's shown istab:selected, and the line under it istab:selected::after's background. A pressed tab has no look of its own: the line goes under it while it's held and back if the press leaves it. The desktop's top row fits its tabs to their labels, and the views share their width. - The phone's views are chips instead: small buttons with rounded ends, each as wide as its label and 32 high, 8 apart, from the left of
views. One that doesn't fit what's left of the width starts a new row 8 under the last, so there are as many rows as the width needs. The tab that's shown is filled with the accent's gradient, and the rest are outlined with it, which xo-app draws; atabwith abackgroundhas the rest filled with that instead, as paradise-phone's are with$chip, the ink at 15% the queue's shuffle and repeat have.tab:selected'scolorand::after's background are the fill's ink and colours as before; atab'scoloris the ink of the others, and at:hoverthe ink under a mouse. The chips are 32 high whatever the row is. In anautorowviewsis as tall as its rows of chips and 4 over and under, and its padding; a fixed height cuts off the rows that don't fit. Scrolled down the tree or the tracks past the first 24 points, the chips go compact over 150 ms and come back as soon as the list goes up or is at its top: 26 high with 12 either side of the label, 6 apart across and down, 1.5 wide outlines and 2 over and under. They wrap the same way, so a row can take one more, and anautorow gets shorter and gives the list the room. There's no word for it: the sizes are xo-app's. Scrolled away from its top, the list under the chips putsviewsin thescrolledstate, for a rule at their foot:views:scrolled::after. The five views are in the order Jump In, Artists, Albums, Tracks and Playlists. After them come more chips: the search's, round with a magnifier in it; the filter's, round with a funnel in it, which opens the filters and is never the one shown, and which with some on is wider by the number of groups that are on, beside the funnel ("2"); and last, on Artists, Albums and Tracks, the sort's, an icon for what the view is sorted by (a person, a disc, a note, or an arrow up or down for the year) and its words after it ("A-Z", "Z-A" or "YYYY"), which sorts the view another way and is never the one shown either. The filter's and the sort's aren't views, so they're bare, as the more chip is: no fill, and where the others are filled a line round them in the others' fill. They have no names of their own either. Withmore: Nonviews, where the chips don't all fit on one row only the first N views and the view shown, in its place among them, the search's and the filter's show, wrapping onto another row if they must, and after them a chip saying more, bare; a tap on it shows them all with it last saying less, and a tap on that folds them again. Where they all fit there's no more chip. paradise-phone saysmore: 2, Jump In and Artists. The words are xo's;moreis a stand-in name. - The desktop's top row is the tabs and the caption's keys, placed in each page's map as the phone's header is. The gear goes to the settings and is
selectedwhile they're up. With a standard page up, the top row over it is paradise's own in the standard look, not the skin's, and no tab is selected. infoshows the song: itstitlewithout what's tacked on the end, thebyline(who, at the left, and what it's from at the right or on a line of its own; withjoin, both on one line), what was tacked on asextra, or the one line the app makes for it assummary. A line with nothing to say gives its room back.- On the phone
timeis how far in andtotalis the other end of the time row: the track's length, or minus what's left, and a tap on it switches them, which the app keeps.totalis gone while the length isn't known and gives its room back. On the desktoptimeis how far in or how far to go, and a tap on it switches them. - Now playing's lyrics, when the track has some with times:
lyricsis the line being sung,lyric, one line of it in the part'sfont; while the track is in a break in the singing (8 s or longer: an intro, an empty line in the lyrics' file, or a wait after a line that the line can't fill) it says a note, U+266A, andlyric-progress, a box a skin anchors overlyric(anchor: lyric center, 34 square), draws a ring in its middle, thecolorfilling it from the top clockwise over the same fainter as the break goes.lyricandlyric-progresskeep their room while the track has lyrics, a line or none, so nothing on the page moves as the song goes, and the group gives it back on a track with none.lyric-beforeandlyric-after, the lines either side, are there for a skin that wants three, each gone while it has nothing. Agive-wayonlyrics, which is an auto row, keeps that much of the room for the fill rows: paradise-phone keeps the cover's 120 and its margins, so the lyrics go before the cover gives up any. A tap on the group, withgo: lyricson it, goes to the lyrics page.lyrics,lyric,lyric-progress,lyric-beforeandlyric-afterare stand-in names. - The lyrics page,
lyrics: a top page left of now playing on the phone, first in the row, there only while the track has lyrics with times and the lyrics are on (the queue'slyrics-toggle, which is there while a plugin that finds lyrics is on and remembered), and the header's arrows step over it when it isn't. Its parts arelyric-lines, with itsscrollbar, and themini-playerthe queue and the library have, so a skin's rule formini-playeris the one look in all three;go: now-playingon itscoverandtitletakes a tap on them back to now playing. A skin arranges the page as any (areas); a skin with nolyricsrules has the standard look's.lyric-linesis the lyrics, scrolled: a row is a line, broken over up towrap Nlines of the rule (12 where nothing says, so a long line isn't cut), or a break, a note in a ring. The song's place in them keeps them in view a page at a time, as a karaoke screen has them: when the row being sung isn't whole in the box, that row is scrolled to the top of it (as far as the last page's top, the last rows the box holds from the first), wherever a finger left the scroll. A finger scrolls them up and down, and the wheel; the song's place stops scrolling them while the line being sung is out of view and a finger scrolled them in the last five seconds, and takes them back after. The scrollbar is over the right edge as a list's is, and shows only while a finger or the wheel moves the lyrics, never as the song does. The ink is the rule'scolor: whole on the row being sung, a little fainter on those to come and fainter still on those sung, and it fades between them in a fifth of a second as the song moves on. A tap on a row goes to it, a hold copies its line (with a buzz) and held on a menu has Copy line, Copy all the lyrics and the page's own; a right click opens that menu. While the page is up the screen is kept on.lyrics,lyric-linesandlyrics-toggleare stand-in names. sourceis where the track playing comes from and how its sound is kept, a line of words after an icon: "Jellyfin · direct stream · FLAC 1234 kbps · 24-bit 96 kHz", "Music · MP3 320 kbps". First where: a folder's or a device's name, or the server's kind (never the server's own name or address); then how it comes from a server, a direct stream, transcoded, or a download of a server's track; then the format and its kilobits a second, the audio's own, its tags and pictures left out; and for a lossless format its bits and rate. What isn't known yet is left out until it is. Where the line doesn't fit its room the app draws a shorter one: the bits and rate go first, then how it comes, then the kilobits, then the format, and last the name alone ends in an ellipsis. It doesn't change as a track's information comes in: the last track's line stays, as the cover does, until the new track's is whole, or for two seconds, and then it changes once. The icon and the words together are placed byalign,gapapart, the iconicon-sizesquare. Its kind is what it plays from,source.folder,source.device,source.serverorsource.download, and the app's icon is Lucide's folder, hard-drive or download, and for a server Jellyfin's mark, a single colour in the text's ink; aniconfor a kind replaces it. It'ssource-localorsource-serveras the track is. The haze's calm under it is its whole width, so it stays put from one track to the next. Whether it shows is the app's: one setting, Playing from, Auto, On or Off, Auto showing it while the library has a server or more than one source. Off, or with nothing playing, it's gone and gives its room back, and a skin that places it has nothing drawn.source,folder,device,serveranddownloadare stand-in names.shuffleandrepeatare the queue's keys, there only where a skin places them. A press onshuffleturns shuffle on or off. A press onrepeatgoes off, all, one (the track playing), off. Each isonwhile it is. Their icons are Lucide's shuffle, and repeat for off and all and repeat-1 for one.shuffle,repeatandonare stand-in names.sleepis the queue's sleep timer, there only where a skin places it. A press opens a menu: 15, 30 and 45 minutes, 1 hour, 1 hour 30 minutes, the last custom length when it isn't one of those, Custom..., which opens the app's own picker page for any length from a minute to twelve hours (a standard page, the same under every skin), and Off while it's set. Set, the music fades out over the timer's last 30 seconds to nothing and pauses as it runs out; the volume isn't touched. Its icon is Lucide's moon, alone in the middle while the timer is off, as a key whose words don't fit. Set, it'sonand says what's left after the moon,icon-sizeandgapapart, as "29:59", counting down by the second. The desktop paradise places it in the queue page's top row, between the tabs and the window's keys, and its main menu has the same times under Sleep timer.sleepis a stand-in name.mini-playeris the phone's mini player: the playing track'scover, itstitlealone, andprev,playandnext, which are the transport's keys,playin the sameplaying,pausedandwaitingstates, andadd, the quick add: it puts the track playing into the quick add playlist, or takes it out while it is in, which is itsonstate. A part the skin'sareasdon't place isn't shown, so paradise-phone has noprev(xo, 2026-11-02): its quick add takes previous's place in the turned card and comes beforeplayon the upright one too. Avisualizerin it is drawn first, behind the other parts and over the glass, in the box itsanchorgives it (anchor: mini-playeris the whole of it, amargin: 1short of the glass's line), with the bars and look of now playing's, at its ownopacityandrate; they're kept inside the card's rounded corners where it has any, and aren't drawn or asked for while the library's select mode has the card faded. The cover is made small once for its size, and with no cover it's a disc, half its side unlessicon-sizesays. The title never scrolls:overflow: marqueeis an ellipsis there.elapsedis how far the track is, a line with nothing to take a finger: itsbackgroundthe whole length and itscolorover what's played, none of it with nothing to seek in,anchor: mini-player bottomin paradise-phone. On the library and the queue it shows only while the queue has something in it and gives its room back otherwise. At the foot of the library and the queue their lists go on under it as they do underselectbar, its margins and all, so it can float with the rows showing round it, and a press under it or in its margins is no row's. On the library with the keyboard up for the search it stays at the foot, under the keyboard, and the rows keep above the keyboard. A tap on a part with ago, as paradise-phone's cover and title have, is that move. A mini player placed in a column of its own beside the lists, as paradise-phone's is turned, is a panel: the lists don't go on under it and its room isn't theirs.mini-playerandelapsedare stand-in names.swipeis the phone queue's button under a row a finger pulls sideways, mail style. More sideways than up or down past the slop, the row follows the finger, and the room it leaves at the end it came from is the button's: itsbackgroundfills the room, and its icon, the app's for its kind, sits in its middlewidthwide (116 where nothing says, the standard look's) at the room's outer end, or by the row's edge once the pull is strong. Let go with half the button showing, the row rests open on it and a tap on the button does it; past a strong pull, half the row or a button and a half, it's done as it's let go. Its kind is what it does:swipe.removetakes the track out of the queue, the row going off first,swipe.addputs it in the quick add's playlist andswipe.take-outtakes it out of there, the row coming back. The rows below a row taken out of the queue close the gap it leaves over about a sixth of a second, at once with the system asked for less motion. Which way does which is the app's, set on the settings page, not the skin's. Beside its icon it says in two small lines what it does, "Remove from" over "queue", or "Add to" or "Remove from" over the quick add's playlist, in the part's ownfontandcolor; they fade in as the pull grows, from a third of the button showing to four fifths, and are whole at once with the system asked for less motion. The icon is at the end of the button the row is at, 6 in from it, and the words start 6 past it and run to 6 short of the button's far end, set against the icon: from its left for a button at the right, to its right for one at the left. A name too long for that ends in an ellipsis. It'spressedwhile a finger is on it resting open. The standard look has the remove in$errorwith its icon in$bg, and the quick add's in the accent.swipe,remove,addandtake-outare stand-in names.toastis the phone queue's line of what the app has just done, "Removed from queue · Undo" or "Added to Favorites", over the page and not in itsareas, so a skin anchors it: paradise-phone hasanchor: mini-player top centerwith a margin that puts it over the card's top edge, and turnedanchor: list bottom center. It's gone while there's nothing to say, and a tap on it is the Undo while it offers one. Without a skin's rule it's a pill 44 high in the page's ink with the page's ground for the words, 24 up from the foot. The desktop's queue has none. A stand-in name.- The queue's
countis one line: how many tracks are in the queue and how long they run, as "Total: 10 tracks · 38:14", or what is left of them as "Remaining: 6 tracks · 21:40" after a tap on it. It is gone while the queue is empty and gives its room back. For a moment after a swipe it says what the swipe did instead, "Removed from queue · Undo" for five seconds after a remove, where a tap on it puts the track back where it was, or "Added to Favorites" or "Removed from Favorites" (the quick add's playlist) for two and a half; then the count is back. Its look is the line's own. peekis the phone's panel under a line of icons: a tap on an artist opens it with their albums, on an album with its tracks, and a tap on its icon again closes it. One is open at a time. Its rows areitems, by kind: firstitem.actions, its quick actions for everything it's of, the keyspick,playandqueue, each the app's icon and its word, Select, Play and Queue; thenitem.album, an album with itscover,titleandsubtitle, the year, in an artist's peek, oritem.track, a track'snumber,titleandlength, in an album's, oritem.entry, a track'scover, its own album's,titleandlength, in a playlist's. A tap on a track plays from it. A tap on an album in an artist's peek opens its tracks under its row, one album at a time, the artist's peek staying open: that album's ownitem.actionsanditem.tracks in apeekinside the peek,peek peek, whose ground, rules and padding are its own round them; an album isexpandedwhile its tracks are open. Only the deepest panel open has itsitem.actions: with an album open inside an artist's peek the artist's own are gone and give their room back, and they're back once it shuts. The quick actions show only while nothing is picked: once anything is,item.actionsis gone from the peek and from an album open in it and gives its room back, the rest of the rows moving up, and they're back when nothing is picked again. A key's icon comes first in its padding and its word after it,gapapart, the iconicon-sizeacross;icongives it a picture of the skin's. A peek of what's hidden, which is there only with Show hidden on, has no Play or Queue, since what's hidden can't be played: itsitem.actionshaspickandunhide, the app's eye icon and Unhide, which shows it again, andunhidetakes the place Play and Queue give back. It is there whatever else is picked. A peek of anything else never hasunhide, and gives its room back. Where the keys with their words don't fit across the row together, each shows its icon alone, in the middle of its padding. The panel is as tall as its padding and its items, each as tall as it says, 48 when nothing says, and the peek inside it as tall as its padding and its own items. Its rules and ground are the panel's own, and its icon isexpandedwhile it's open. The line it's under grows to hold it and shrinks back, over a moment, as it opens and closes, and as an album's tracks open and close in it.peek,item,queue,pickandunhideare stand-in names.- In a row or an icon:
title,artist,album,length,name,subtitle,cover,fold(shows only on a row that can open),editwithselectionandcaret(only while a row is renamed, in the name's place). A row's and an icon'sbadgeis a slot for add-ons' marks and draws nothing of its own (see Add-ons). - A queue row's and a library track row's
coveris the track's own album cover, small, in the row's map where the skin puts it, and it's there only where the map names it. With none it shows the app's disc, or the skin'sicon. A playlist's track in a peek (item.entry) has the same. The shipped skins put it 36 square with square corners at the row's left, in the key's tone where there's no picture, as the peek does. The desktop's tracks table has none: its columns are the app's. Covers are asked for as a row comes into view and given up when it's scrolled past before they arrive. - Playlists is the tree as icons too: paradise's own playlists by name, then the servers', each its first track's cover, its name and how many tracks it has as its
subtitle; a server's that can't be reached now isoffline. A tap peeks at its tracks as an album's does. A playlist's tile is anicon.playlist, and the quick add's playlist, Favorites, anicon.favorites; with no cover the app draws a list with a note in the first and a heart in the second where an album has a disc, unless the skin'scoversays anothericonoricon: none. Favorites with nothing in it isn't a tile.icon.playlistandicon.favoritesare stand-in names. The first tile of Playlists, when there's no search, is anicon.new-playlist: itsnameis "New playlist", it has nosubtitle, a tap on it makes a playlist, and with no cover the app draws a plus where an album has a disc, unless the skin'scoversays anothericonoricon: none. It can't be picked or held.icon.new-playlistis a stand-in name. - Jump In is the tree as icons in two sections, Recent and Most played, of what was played: an album, an artist, a playlist, a track, a folder or a source, each as the
iconit would be on the Albums or Artists tab (anicon.artistis a person for an artist, a playlist is anicon.playlistoricon.favorites, every other tile is anicon.album), itsnamewhat it's called and itssubtitlewhat it is in a word. A section starts with anicon.headingon a line of its own, itsnamethe section's words; the line is as tall as the heading says withheight, and as wide as the tree.icon.headingis a stand-in name; a skin that says nothing of it gets the standard look's. - The phone's library shows one of
tree,tracksandemptyat a time, under its views: the tree as icons on Artists, Albums, Jump In and Playlists and as rows on what the search found (and on Playlists while one is renamed, its name typed in its row), the tracks on Tracks, andempty, the words for a view with nothing in it, on Jump In till it has something and on the search till it's found something. The other two are hidden and give their room back.emptyis a stand-in name. - Select mode is the phone's: tracks picked out of the library for Play or Add to. While it's on, every artist, album, track and playlist in the library has a circle, its
select, which xo-app draws in the middle of its box in the accent's gradient: a ring when none of its tracks are picked, a dot in it when some are, filled and a little bigger when all are. Out of select modeselectis hidden and gives its room back, and as the mode comes and goes the circles fade and a map's column of them opens and closes. A press within 22 of a circle's middle is the circle's, past its box if it's smaller, but never past the tile or row it's in. A peek's quick actions have none. A skin placesselectin a row's or an item's map or anchors it; where it doesn't, the standard look puts a row's and an item's at its left and an icon's in its cover's top left corner. A circle is drawn on a solid disc, its ownbackgroundandradius: paradise-phone makes it 30 across, 3 larger than the ring, in the ground it's over, which on a cover is$panel, so the ring reads on any picture and a cover with no picture shows no smudge, on a row the page's$bg, and in a peek$panel. A row, tile or item with some or all of its tracks picked ispicked: its circle has its dot or is filled, on its disc, and the app draws nothing else for it. The library's own selection, the row last held or tapped, is not drawn on the phone: it means nothing there, and paradise-phone has no rule for it. Where a skin says nothing the standard look does the same with the theme's own tokens. selectbaris select mode's bar:count, how many tracks are picked or for a moment what was just done with them, and its keysselect-all,play,add-to,moreandclear, which say Select all, Play, Add to, More and Clear. Select all picks every track the filters keep, and is there only while a filter is on. Play and Add to open their menus for the picks, More a menu of the rest of what can be done with them, what a hold's menu had for their tracks but what the bar has keys for (sending them to a device, deleting their downloads, looking for a cover, the plugins' items), and Clear lets them all go, which ends select mode. On the phone a hold on an artist, an album, a track or a playlist picks it as the finger lets go, select mode coming on with it. Where the count doesn't fit beside the keys it goes on a line of its own over them, as tall as its font's line with the bar'sgapover it, and the bar is that much taller; where the keys' words don't fit across the bar even alone, or the skin sayslabel: none, as paradise-phone does, each key shows its icon alone, the app's, Lucide's list-checks, a filled triangle, list-plus, ellipsis and an X, as tall as its words unlessicon-sizesays; they keep their column at the bar's right. It's needed: a skin that leaves it out gets it at the page's foot. It comes up from under its foot as select mode starts and goes back down as it ends, and gives its room back out of it. paradise-phone's upright is as tall as the mini player's room and takes its place:mini-player:pickedanchors the mini player over it,anchor: selectbar bottom, and fades it out withopacity: 0%as the bar fades in; turned the bar is an island, a pill with its own ground, margin and shadow, over the rows, which still scroll clear of it, and itscount, whichanchor: library bottom lefttakes out of the island to the page's foot, left, under the mini player. Acountanchored is neither beside the keys nor over them.select,selectbar,select-all,add-to,moreandclearare stand-in names.- The phone's
searchshows only while the library is searching, between the views and the rest, and gives its room back otherwise. What it found is the tree's rows: a heading each for the artists, the albums and the tracks, which arerow.headings and do nothing when pressed, with what's under it a level in. Their height is the row's height of that kind, which can be shorter than the rows'. Once there's something in it, itsclearis an X that empties it, in what the search's padding leaves at its right: how wide it is. It takesicon-size,colorandalignas a key does. - On the desktop the tracks are a table beside the tree. A row's cells are
number,title,artist,album,lengthandother, the column the app shows any other field in, which only comes while there's one. The app lays the columns out from the row's map: which show, their widths, the one dragged between them and the sort are the app's, the map's widths where they start.columnsputs alabelover each,#, Title, Artist, Album and Time, nothing overotherbut the field's name. The column the tracks are sorted by islabel:sorted. splitis a column of the library's map that the app drags. It starts where the map puts it and moves between 200 from the page's left and 300 from its right. The app keeps where it's dragged to. Itsgripsits in its middle unless the skin places it.searchis the library's text field: its icon in the middle of its padding's left, its words inedit, and with nothing typed, "Search" in its own colour. It'semptywith nothing typed andfocusedwhile it has the keys. Itseditfills it inside its padding and shows while there are words or it's focused, the caret only while it's focused.countis what the desktop's library is doing, or how many tracks show; inselectbar, how many are picked.bufferis how much of a track from a server is in, from its start: the track's shape in its own paint, cut where what's in ends, over the track and under the fill, as the fill is cut at the knob. With no size known, as a transcode, it's guessed from the bitrate. A track on this device has none, and a skin without abufferdraws none. A stand-in name.volumedraws its icon at its left, in the middle; its pieces go as the seek bar's.buttonis a skin's own key for a move:go: library,go: back. Now playing has one, and the phone's queue can, drawn over the rest of the page. It shows alabelor anicon. See Properties.- On the phone's now playing
addin thetransportis the quick add, as the mini player's: a press puts the track playing into the quick add playlist or takes it out, and itsonstate, while the track is in it, is where a skin gives it the minus. Where a skin lays it inacrossit's the first of the keys: paradise-phone hasacross: add prev play nextwithcenter: play, and the queue'sbutton,anchor: next center leftwith a margin of next's width and the gap, as the row's last key, on the same line.footis an empty box a skin sizes under the keys, the room the visualizer stands in; paradise-phone's iswindow.height - 240high, 0 to 64, so a page too short for the bars has none. They're a stand-in name. peekon the phone's queue rests it on now playing while it's down: the part it names, the queue'sbutton, a grabber say, shows over now playing's key withgo: queue, the two tops together, with the queue's own ground under it and on to the window's foot under the system's bar: what the queue's page has for a background, drawn where it is over the window, so with ahazeit's the queue's haze, calm and all, the same cloud the queue has there once it's up, so the peek and the queue below it are one surface with no step between them whether it's down, coming up or up, withpeek-opacitysaying how much of it shows at rest. The peek's ownbackgroundgoes over that, sobackground: noneshows the queue's ground, and a skin that wants no line across the bar leaves out its borders. Nothing else of the queue shows and now playing isn't dimmed. The queue's ground under the peek is drawn on its own, from the sheet's top down in one piece, soradius-toprounds the sheet's top corners where the peek has them, andpeek-opacitymakes the ground only a part as strong at rest, over now playing; the pull brings it to whole in a straight line overpeek-opaqueheights of the peek and the corners keep their roundness all the way up to the lastradius-toppoints, with now playing, dimmed, in the cut-away; there they shrink with its line to nothing, so that flush with the window's top it has neither. The key under it still takes the presses, so a tap or a swipe up there opens the queue, and the peek is drawnpressedwhile the key is. Coming up, the queue goes from there to its place, not from below the window: the ground under the peek shrinks to nothing on the way, the rest of the queue coming up under it rather than under the ground, and what's above the peek comes into sight. Going down it comes back there. Withpeek-fadethe peek fades out as the queue comes up and the rest of the queue, all but its ground, fades in over the same pull; up, the peek isn't drawn, though a tap where it is still takes the queue down. Put the key at the page's foot, and make the peek as tall as it, or taller with apeek-fade: what's past the key's foot is under the window's at rest. A key narrower than the peek, a tab under the keys in a column of its own, keeps the queue to its columns at rest: the peek and its ground are drawn there only, the peek's middle moved to the key's andradius-toprounding the tab's corners, so nothing of the queue shows beside it. Pulled up, they widen to the window's in a straight line as the ground gets whole, overpeek-opaque, and the queue below comes up in the same columns. Where now playing's key isn't placed, or the peek isn't, the queue comes from below the window as it does with nopeek. paradise-phone has none: its queue comes up from below the window, by now playing's key in the corner or a swipe up.peekis a stand-in name.- The window is the app's: 920 by 600, at least 760 by 520, with resize grips 6 at the edges and 18 at the corners. The old skin's numbers, and stand-ins. Its edge is drawn by the skin, a
borderon each page. visualizeris now playing's, on both layouts: what the music being heard looks like, drawn under everything else on the page, in time with the sound rather than with what's being decoded. It draws what itsvisualizerproperty says, and nothing without one.visualizer: bars 64is 64 thin bars, 32 unless it says, each a band of the sound from the lowest to the highest, evenly spaced in pitch from 40 Hz to 16 kHz, standing on the part'sedgeand as long, toward the part's other side, as that band is loud.edge: bottom, as when nothing says, has them side by side along the foot rising up, the lowest at the left;tophangs them from the top;leftandrighthave them one over another up that side growing in toward the middle, the lowest at the foot;edge: left righthas the same bars on both sides, facing in.order: mirrorhas the lowest band in the middle of the edge instead, each band twice, up from there out to both ends;order: sweepis the start-to-end way.reachis how far one goes at full, the part's depth from its edge when nothing says or half of it with two edges.taper: 4 roundmakes a mound of them: the bars in the middle reach as far asreachsays and the ones at the ends only 4, rounding down between;lineargoes in a straight line anddomestays full at its shoulders.gapis between the bars, 2 when nothing says; withgap: 0they meet with nothing between, each line of them drawn as one piece, so they're one shape with no seams at any scale. The bars are in theircolor, the accent's when nothing says, laid over the part: a gradient across runs across the bars and one down runs up each of them,linear(down, $blue, $green)green at the foot and blue at the top of the part.tip: 3 50%fades the last 3 of each bar to half strength at its very end, to soften it.ride: peekputs it on the top edge of the sheet resting on the page, the phone's queue: it's drawn with the sheet's peek, over the page, goes up with the sheet's top as the sheet's pulled up and fades out as the peek does withpeek-fade, and while the sheet is up it isn't drawn and costs nothing; where no sheet rests on the page, as turned withpeek: none, it's drawn where it's placed. paradise-phone's stands on the window's very foot under the keys,anchor: now-playing bottom left, the window's whole width upright and the seek bar's track's turned, reaching up to$spshort of the play key's foot,height: (window.height - play.bottom - $sp), swept with the lowest band at the left;order: mirrorthere puts the lowest in the middle, and in the dark it's a glow laid over the haze with its own alpha,opacity: 80%,blur: 2andzoom-blur: 8 8% 8%fromzoom-center: 0 400%;blend: screenthere would lay it as light instead. In the light it has no glow, as a darkened colour smeared up and out reads as a beam turned inside out: it has abars-shadowof its own shape under it, the bars whole over it ($bars-light-opacity).riseis how long a bar takes to close the gap to a louder band andfallhow long it takes to fall the whole way down, in milliseconds, 40 and 450 when nothing says, 0 for at once. A smooth skin's bars move a screen pixel at a time and a pixel skin's a skin pixel. The bars move only where the GPU draws the window and the system isn't asked for less motion; anywhere else, and while nothing plays, there are none, and they cost nothing. A skin with one asks paradise for the GPU, as a transition does. Their bands are 0 at 66 dB under full scale and whole at 6 under, tilted 3 dB an octave up from 1 kHz, as music is quieter going up. Avisualizerin themini-playeris the same part behind the card's own: paradise-phone's isanchor: mini-playerwithmargin: 1, 80 bars (48 turned, fromanchor: mini-player bottomandheight: 45%, standing on the card's foot), the mound, tip and green-to-blue of now playing's from the same$bars-end,$bars-tipand$bars-tip-strength,opacity: $mini-viz(25%, so the card's words and keys read over it) andrate: 30.opacitymakes the bars that strong, their colour's own strength times it.rate: 30is the most frames a second the bars ask for while they move, 1 to 240 ornonefor every refresh, as when nothing says: where they're all that moves on the page, as behind the library's lists, the loop is given that rate and not the display's, and anything else moving, a finger or a glide, still gets every refresh.blend: screenlays the bars as light over the haze, a glow that lifts what's under it rather than bars covering it; withblend: normal, as when nothing says, they're laid over with their own alpha.blursoftens them andzoom-blurwithzoom-centersmears copies of them out from a point under them, with either blend. paradise-phone's mini player has that glow in the dark, the sameblur,zoom-blurandzoom-centeras now playing's, at its own quiet 25%, and in the light the samebars-shadowas now playing's but for its own strength,$mini-shadow-alpha, so the shadow isn't heavier than the bars it's under. A part made into a picture in another part, as this one is in the card, is kept to that part's box less its own margins, the blur and the copies and all, so the glow stops at the card's edge; on the page itself, as now playing's is, it reaches as far as it does. The clip is a rectangle: a card with round corners keeps the bars (not the blur) inside the curve.visualizer,edge,reach,rise,fall,order,taper,tip,rideandrateare stand-in names, and so aresweep,mirror,roundanddome;barsis the one kind for now.
skin.pss - Arranging
A page, a part, a row or an icon arranges what's in it with an area map, from CSS's grid-template-areas:
now-playing {
areas:
"header" 68
"cover" fill
"info" auto
"time" auto
"seek" 36
"transport" auto;
landscape {
areas:
"cover header" 68
"cover ." fill
"cover info" auto
"cover time" auto
"cover seek" 36
"cover transport" auto
/ square 1fr;
}
}
- Each line is a row of named areas in quotes and its height: a number,
%of the room, a share infr,auto(what its parts need),fill(an equal share of what's left),fit, or a bracket. Afitrow is afillrow that takes no more than its parts can use, a square cover as tall as it's wide: it takes that, or what's left, before thefillrows share the rest, sofillrows either side of it hold the room it doesn't need. With nofillrow it's one. paradise-phone's cover isfitbetween twofillrows, so on a tall phone the cover and the song under it sit together in the middle.fitis a stand-in name. A row with no height isauto. Every row has as many areas. - Column widths come after a
/, one for each, the same sorts andsquare, a column as wide as the rows its part spans are tall, held to the part'smax-width. With no/every column is1fr. .is an empty area. A name is a part inside the one being arranged, and a part's areas make one rectangle; anything else is a mistake.- A share column keeps its least: the widest
min-width, with its margins, of a part alone in it. The other columns give theirs back from the right, down to their own least. Afillrow keeps its parts'min-height. - A row or a column whose parts are all hidden gives its room back. One with parts of its own, in that row or column alone, gives it back once those are all hidden, whatever goes across it into others: paradise-phone's mini player down the side of the library's tree, tracks and empty rows turned doesn't hold open the two that aren't shown. A hidden part is one with nothing to show: an empty line of the song, the library's tree while it shows its tracks.
- A map that comes to less than its room sits in it by the part's
align. - A size that comes to less than nothing is 0.
- A
landscapeblock is a rule of its own for the same part, counted when the phone is turned, a window wider than it's tall, and then it beats the plain rule. It goes inside a rule, and can hold rules. On the desktop it never counts. - A map is one value: the
areasthat wins is the whole map, not merged row by row.areas,acrossanddownare one arrangement, and the one that wins arranges the part. - A small group goes
acrossordown, its parts in the order named:transport { across: prev play next; gap: 8; }. A part in it can have awidthorheightinfr, a share of what's left.center: playkeeps one part in the middle and lays the others out either side of it. infoarrangeddownis the song's lines one under the other, as tall as they need, giving up lines from the last where there's no room, and all of them where not even the first fits. In a map its row is measured after the otherautorows, so it's the song's lines that give way before the keys or the time.give-way: 64lets a part in afillrow give way where the rows come to more than the room: it gives up itsmin-heightfirst, down to that, and below that it's hidden and its row gives its room back, before anautorow is cut short. paradise-phone's cover gives way to 64 on a short page and then goes. In anautorow whose parts all have it,give-way, whatever its size, has the row measured after the song's lines, and gone, giving its room back, where what's left is less than it needs, so the song keeps its lines: paradise-phone'ssource.give-wayis a stand-in name.- Where a room is too short even then, a part in an
autorow with less room than it needs isn't shown, rather than run into the next row or under the window's bars. gapis even spacing, between rows and then columns, one number for both. Anything else is a part'smargin, in CSS's order, and a margin can be less than 0.- A part sizes to its content unless it's given a size, and in a map it fills its area the way it isn't sized.
- The safe area, the phone's bars and notch, is outside the page's map. A part whose background should run into it says
bleed: top. The phone library'streeandtracksgo on under the bars at the foot themselves, and so does the queue'slistwhilemini-playeris under it, where nothing butmini-playerandselectbarare under them, one on the other: their rows scroll on to the window's foot, and the room under the bars, and the room the map givesmini-playerandselectbar, margins and all, is padding of theirs their last row still comes up above. Amini-playerplaced by an anchor, overselectbarin select mode, isn't one of them. aspect: 1keeps a part square at the most, the longer side giving way. With one size given it takes the other from it, not from the area it's stretched in.min-andmax-sizes bound it.anchor: cover top rightputs a part over another's drawn box, the cover's being its picture's, and thenmarginmoves it in. Its edges areleftorright,toporbottom, andcenterfor a way the others don't name. A way nothing names runs the other part's length, its margins in from each end. With no part named it's over the part it's in.anchoris only for parts over other parts; an anchored part isn't in the map.- A list's scrollbar sits over the list's right edge and its rows, under its
columns, as wide as it says or xo-app's 8, and shows when the rows don't fit; its thumb is$thumb. Its width is how far in a press finds it: wider than 8, the thumb is still drawn at its right edge as in a bar of 8, so a wide one is only easier to grab. paradise-phone's is 44, as its scrubber is. A finger on it never swipes the pages or a sheet. While they fit it takes no press: a press there is the rows'. On the phone the thumb shows only while the list moves, under a finger, flung or held, and for a second after, then fades out over a quarter of one; faded, it takes no press either. The times are xo-app's. - On the phone,
scrubberis a strip over the right edge of the Artists and Albums tabs' icons and the Tracks tab's rows, as wide as it says or 32, on top of the scrollbar. While the thumb shows, a finger on it, the thumb or anywhere down the strip, takes the list a letter at a time: down the strip is down the list, and the list goes to the top of the letter there, the artists' first letters as the list sorts them (on Albums, the albums' artists'), case folded and with no article left off, a digit or a mark "#". While it's held the thumb is as wide as the scrollbar, and itsbubblegoes with the finger:margin-bottomabove it (64 when not given) and endingmargin-rightleft of it, or where that's off the top of the screen, beside it on its left,margin-bottomfrom it and its middle level with it; kept on the screen clear of the system's bars, as big assizesays, with the letter in its middle in itsfontandcolor. Abubblewith nobackgroundorborderis xo-app's own, a disc of$key64 across, the letter in$headand$text. Not on any other list, and not while the thumb has faded: then a finger there is the rows'.scrubberandbubbleare stand-in names. - A tree row's
indent: 16 12is how far in the first level starts and how much more each level after. One number is both. paradise-phone saysindent: 0, so a search's results are in line with its headings. - Every row of a list is as tall as its
rowsays, with no states; a heading is the same. 48 when nothing says. Icons are at least as wide asiconsays, as many across as fit, sharing the width, and as tall as it says. The first starts the icon's leftmarginin from the list's left and the last ends its rightmarginin from the right, the list'sgapacross between them:tree { gap: 12; }tree icon { size: 85 (width + 64); margin: 0 20; }is as many as are 85 wide or more, 20 in from the sides and 12 apart. In a bracket in an icon'sheight, or in a row of its map,widthis the icon's own, so a square cover can fill it:"cover" (width). With no margins and no gap, the width is shared out among as many as fit.
Anything a skin doesn't say for a part, the standard look's rule for it can, its padding, margin and align too, even where the skin arranges the page itself.
skin.pss - Rules
play { ... } // the part, on every page it's on
now-playing play { ... } // on one page
play:pressed { ... } // in a state
prev, next { ... } // several at once
row:current::before { ... } // a layer, in a state
icon.album { ... } // a kind
A selector is the parts from the outside in, each anywhere inside the one before it, the last the part the rule is for. A first word that's a page's name means the page; one that's settings, plugins or plugin is a mistake. Rules nest, so now-playing { play { ... } } is the second form. A rule inside a layer is a mistake. A selector that names nothing on any page is a warning.
- The tracks are the library's on both layouts:
library tracks. - A part the app has in kinds names the kind after a dot:
icon.artist,icon.album,icon.playlist,icon.favorites,icon.new-playlistandicon.back, the icon that goes back out of an artist;item.actions,item.album,item.trackanditem.entryin a peek;source.folder,source.device,source.serverandsource.downloadon now playing;icon.heading, the words that start a section of Jump In's icons;row.headingin the tree, the words over what the search found; andswipe.remove,swipe.addandswipe.take-outon the queue. ::beforeand::afterend a selector. See Properties.- Page names come from the app in neutral case, "Now playing";
text-transformsets it.
States are the app's, and a part has the ones that make sense for it: pressed hover selected current playing paused disabled focused expanded chosen unplayable offline downloading waiting drop-target editing empty sorted source-local source-server art-tags art-folder art-server art-musicbrainz art-deezer art-itunes art-discogs art-lastfm picked scrolled on hidden. Any other is a mistake. picked is also the phone library's mini-player while select mode is on and while it's still going out. scrolled is views', while the list under it is scrolled away from its top. on is the queue's shuffle and repeat while they are. hidden is a library row or tile, a peek's album or track, a tracks row or a queue row the user hid, there because Show hidden is on; it doesn't play, and paradise and paradise-phone draw it at opacity: 40%. selected is also the tab that's shown, views tab:selected and on the desktop tabs tab:selected, and the desktop's gear while the settings are up. playing and paused are the play key's, and so is waiting while the track playing is held up, its sound waiting on its server for more than 400 ms; with icon: throbber it turns xo-app's throbber in its mark's place. sorted is the column the tracks are sorted by, columns label:sorted. A fold is expanded with its row. Where an icon's music is, source-local or source-server, and where its cover came from, art- and its tags, its folder, the server or the cover plugin that fetched it, are an icon's states in the library; a tracks row has where its music is, and the status bar both for the track playing. An add-on's marks in them have them too.
skin.pss - Order
One model decides every clash: rules, tokens, @dark, landscape, a variant over its skin. Where two declarations set the same thing on a part, they're compared in turn, and the first that differs decides:
- The selector naming the page beats one that doesn't.
- Then the one naming more states and kinds. A state named twice counts once.
- Then the one naming more parts.
- Then, while they hold, the one under more of
@darkandlandscape. - Then the later. A variant is read after its skin, as if written below it.
Nothing is added up. queue row beats row:selected, which beats tree row, which beats row. So a selected row in the library's tree that should look selected says so there: tree row:selected. Between row:selected and row:downloading on a row in both, the later wins, so a skin orders its rules that name as many states the way it wants the clash to come out.
- In a selector list each selector ranks on its own, and the rule ranks by the best of those that match.
- A top-level token names no part, so only 4 and 5 rank it: in the dark
@dark's$bgbeats the plain one, whichever is later. @darkandlandscapeonly decide between rules that say as much: a plainnow-playing playbeats@dark { play { } }in the dark too.- Each property is ranked on its own, so a later rule doesn't undo an earlier one's other properties. A property that sets several ranks with each of them:
sizewithwidthandheight,borderwith each side. - What flows,
font,colorand tokens, comes in only where nothing for the part itself sets it, from the nearest that does: the part a layer is of, each part outside it, the page, and for a token the top of the file last. - The skin beats the standard look on a part, however little the skin's rule names. For what flows the two are asked a level at a time, so the standard look's rule for the part itself beats a skin's rule further out.
skin.pss - Order - Exceptions
- What a part sets itself beats what flows in, however much the rule outside names.
now-playing { color: #fff; }doesn't reach aninfowith a colour of its own, thoughinfodoesn't name the page. It's CSS's inheritance. areas,acrossanddownrank as one, since a part can't be laid out two ways at once.- A font token that finds nothing is
$body, as Tokens says. @skin,@variant, the transition's block and each name in them are said once, and a second is a mistake.@font, tokens and rules can repeat, and the later of two that rank the same wins, in a file or across a variant.
skin.pss - Properties
A property in a rule has to be one of these, and a value it can't take is a mistake.
| property | |
|---|---|
width height |
A size: a number, %, fr in a group, auto, fill, a token or a bracket. size: W [H] is both, H the same as W unless it says. |
min-width max-width min-height max-height |
Bounds on the size. |
give-way |
How far under its min-height a part in a fill row shrinks on a short page before it goes; in an auto row, that it goes before the song gives up a line. See Arranging. |
padding margin |
One to four sizes, in CSS's order. A side's % is of the room's width. |
gap |
Between rows and then columns, one size for both. On a tree, its second is between icons across. |
align |
One or two of left, right, top, bottom and center: where a part smaller than its area sits, and its words and icon in it. center alone is both ways, beside another word the other way. The start unless it says. |
aspect |
Width over height, 0.01 to 100. |
anchor |
[PART] EDGE..., or none to put a part a plainer rule anchored back in its map, as paradise-phone's turned mini player does with elapsed. See Arranging. |
avoid |
PART. A line of text stops short of the part named, one anchored over the end of it, where the two overlap, by the part's left margin; it has its room back while the part isn't shown. paradise-phone's byline avoids modes. A stand-in name. |
bleed |
One to four of top, right, bottom and left: the background runs on to the window's edge that side. none puts it back in its box, as a landscape block can. |
indent |
A tree row's first level and each after. |
areas across down center |
See Arranging. |
background |
A colour, a gradient, none, a picture, a picture nine-sliced with "keys.png" slice 4 (corners kept, edges and middle stretched), or on a page haze or backdrop. haze on a part is the page's haze with the part's numbers: see Backgrounds Made from the Cover. |
border, border-top border-right border-bottom border-left |
WIDTH COLOR [inset N], or none. One colour or a gradient, at most 1000 wide, drawn inside the part's box. The inset stops it short of the ends, as the rule under a row does. A round part with the same border all round gets a ring, in the gradient if it is one; straight sides take a gradient's first colour. A round part with a border-top alone has it go round its top corners, down to where the rounds end and no further, one crisp line along the top at any density; on a page with radius-top, the sheet's outline. |
radius |
A number, or 50% for round ends. A % is of the shorter side. It rounds a picture background too, and on a cover the cover's picture, a % of the picture's shorter side. |
radius-top |
A page under another, the phone's queue: how far its top two corners are rounded, in points, with a haze or a colour for a background. The page under it shows in the cut-away while it's coming up. The corners, and the line round them that border-top draws, shrink and fade over the last N points before the sheet's top meets the window's, and at the top, flush, it's square with no line: a sheet that is up has nothing of the page under it showing along its top. 0, the default, is square. |
shadow |
X Y BLUR COLOR, as CSS's box-shadow: the part's box, its radius and all, moved X and Y, softened by BLUR, under its background and only outside it. A gradient gives its first colour. In a pixel skin it's hard edged, a skin pixel there or not. |
opacity |
On a row, an icon or a peek's item: all of it drawn that strong, as a percent, 40%, its cover, words and marks together as one, but for its select circle, which is drawn whole over it so a dimmed row can still be picked. On the phone library's mini-player, select mode moves it from what its rule says to what mini-player:picked says as the mode comes in, and back as it goes, over a fifth of a second and in a step with reduced motion; at 0% it's not drawn. On a visualizer, how strong its bars are, their colour's own strength and their tip's too times it. Without it, whole. |
color |
Text and icon ink. With nothing saying, $text. |
font |
A font token, or a @font's name and its size, 1 to 256. With nothing saying, $body. |
icon |
A part's own icon in place of the app's, a picture, or none for no icon. throbber is xo-app's throbber in a key's icon's place, at its icon-size and in its color, as play:waiting has it. |
icon-size |
How big the icon is drawn, at most 1024. With none, as big as fits. |
text-transform |
uppercase or lowercase, of ASCII letters. |
overflow |
A line too long for the part. ellipsis, as when nothing says, ends it in the font's ellipsis, or three full stops in a font without one. Text that is cut starts at the part's left whatever align says; text that fits is placed by align as any text is. clip cuts it at the part's width, marquee scrolls it when it's too long, wrap N breaks it over up to N lines, leaving off what doesn't fit, and wrap N ellipsis ends the last of them in the ellipsis where there's more, or a word too long for its line in it there, unbroken. Only now playing's title scrolls, waiting 2250 ms at each end and moving a character every 225 ms; any other marquee is an ellipsis. clip is a stand-in name. |
progress |
How a row or a cover shows its download while it's downloading: border COLOR a line round it as far as it's got, fill COLOR filling from the left, or none. |
pixelated |
yes draws now playing's cover in blocks; whole at the biggest whole multiple of its own pixels that fits, centred. |
appearance |
none keeps the part, its look and what it does, and draws nothing of the app's in it. |
haze |
The haze's numbers. See Backgrounds Made from the Cover. |
go |
A button's move: a top page, settings, plugins, back, or a sub-page of the page the button is on. plugin is a mistake: a plugin's page opens only from plugins. On another part, as the mini player's cover and title, a tap there is the move; a part inside one with a go is that move too, so put it on what has no key in it. |
label |
A button's words, in quotes. On a key with words and an icon, the select bar's or a peek's, none shows its icon alone. |
peek |
On a page under another, the phone's queue: the part of it that rests on the page above while it's down. See Pages and Parts. A part that isn't the page's, or a page that isn't under another, is a mistake. |
peek-opacity |
On a page under another with a peek: how strong its ground is while it rests on the peek, as a percent: 25%. It rises in a straight line with the pull to whole at peek-opaque, and falls the same way going down. Only the ground, its haze or colour, with the corners: the page's words, keys and rows, and the peek itself, are drawn whole. Without it the ground is whole. |
peek-opaque |
On a page under another with a peek: how far it has to be pulled up for its ground to be whole, in heights of the peek part, 3 for three, which can be a fraction. The ground goes from peek-opacity at rest to whole in a straight line over that distance and stays whole from there to the top; going down it's the same way back. Without it, the line runs the whole way up. |
fade-foot |
On a list that goes on under what's at the window's foot, the phone's mini player and the system's bars: its rows there fade into the page's ground, whole at the top of what's over them and gone this far short of the window's foot, in a straight line, the ground whole from there down. paradise-phone's lists say 8. A stand-in name. |
more |
On the phone's views: how many of the views stay, with the one shown and the search's and the filter's chips, behind a more chip where they don't all fit on one row. See Pages and Parts. A stand-in name. |
peek-fade |
On a page under another with a peek: as it's pulled up the peek part fades out and the rest of the page, all but its ground, fades in. At rest the peek is whole and the rest isn't drawn; once it's gone this many heights of the peek, 2 for two, the peek is gone and the rest whole, in a straight line, and going down they fade the same way back. Up, the peek isn't drawn, but a tap on it is still its. Without it both are drawn whole all the way. A stand-in name. |
visualizer |
bars [N] or none: what now playing's visualizer draws. See Pages and Parts. A stand-in name. |
edge |
One of bottom, top, left and right, or two opposite ones: where a visualizer's bars stand. A stand-in name. |
reach |
How far a visualizer's bar goes from its edge at full. A stand-in name. |
rise fall |
How long a visualizer's bar takes to rise to a louder band and to fall the whole way, in milliseconds, 0 to 10000. Stand-in names. |
order |
sweep or mirror: which band a visualizer's bar shows, the lowest at the start of its edge, or in its middle and out to both ends. sweep when nothing says. A stand-in name. |
taper |
SIZE [linear|round|dome] or none: how far a visualizer's bars at the ends of its edge reach at full, the middle's reaching reach, and the curve between, round when it doesn't say. A stand-in name. |
tip |
SIZE N% or none: the end of each of a visualizer's bars, that long, fading to N% at its very end. A stand-in name. |
ride |
peek or none: a visualizer drawn on the top edge of the sheet resting on its page, going up with it and fading with its peek. See Pages and Parts. A stand-in name. |
rate |
HZ (1 to 240) or none: the most frames a second a visualizer's moving bars ask for, every refresh when nothing says. paradise-phone's mini player's asks for 30. See Pages and Parts. A stand-in name. |
blend |
normal, screen or add: how a part and everything in it is laid over what's under it. normal when nothing says, as it's drawn; with a blur or a zoom-blur it's made into a picture first and laid over with its own alpha, so it can darken as well as lighten. screen and add make it light, drawn over black and laid so it only brightens: screen lifts each colour toward white, most where it's dark, add adds them up to white. Black is nothing, and over white light shows nothing. A stand-in name. |
zoom-blur |
N SIZE% STRENGTH% or none: on a part made into a picture, N copies of it under it, 1 to 8, each bigger about the zoom-center, the last SIZE% bigger and the first STRENGTH% as strong, each after it fainter down to an Nth of that. They reach past the part's box. Light with blend: screen or add, laid over with their alpha with normal. A stand-in name. |
zoom-center |
ACROSS DOWN: where a zoom blur is made bigger about, from the middle of the part's box, each a size or a percent of the box's width or height. 0 200% is twice the part's height under its middle, so the copies smear up and out. 0 0 when nothing says. A stand-in name. |
blur |
SIZE: on a part made into a picture, a plain blur of the whole of it, about SIZE points wide, made before the zoom blur copies it. The picture is made at that much less size, so it costs less than none does, and laid up to its own, filtered; 8 pixels at the most. 0, as when nothing says, leaves it sharp. Laid over with its alpha with blend: normal, as light with screen or add. A stand-in name. |
bars-shadow |
X Y BLUR DARKER% ALPHA% or none: on a visualizer, a shadow of its bars' shape laid under them, moved X across and Y down (negative for left and up) and softened by BLUR, 8 pixels at the most. It's in the bars' own colours, the ones the cover and the haze guard make of them, each made DARKER% darker in lightness (OKLab) and ALPHA% strong, so it follows the cover; a part's shadow can't, its colour is fixed. It's a picture of its own, so opacity doesn't thin it. A level of the quality setting with no fx has none. paradise-phone says it for the light theme, from $bars-shadow-x, $bars-shadow-y, $bars-shadow-blur, $bars-shadow-darker and $bars-shadow-alpha at the top of its skin, and bars-shadow: none with the glow back in @dark. A stand-in name. |
join |
A byline's words between its artist and its album, in quotes. With it they are one line, the artist then the join then the album, each in its own ink, the album going first where the line is too long for the room and gone with the join where there isn't room for a start of it; gap and the album's place at the right and on a line of its own are no longer in it. |
Layers come from ::before and ::after: row:current::before { width: 3; background: $accent-v; anchor: row left; margin: 12 0; }. A layer's box is its part's unless its own size, anchor and margins say otherwise. It holds a background and a border, with their radius, under the part's own drawing for ::before and over it and what's in it for ::after. A layer with nothing to draw draws nothing, so a skin can give every row a ::before stripe and colour it only where a state asks.
skin.pss - Backgrounds Made from the Cover
background: hazeis the cover as a soft cloud of its colours behind the page, over$bg. Its numbers are the app's: saturation 15, value 30, calm 70 and feather 72 in the light, and 20, -25, -70 and 72 in the dark. A skin changes any of them withhaze: saturation 15 value 30 calm 70 feather 72. Calm is how much more the value moves under the words, and feather is how far that spreads, in points: each line of words casts a soft shadow, asshadowdoes with that blur, from its ink, about a character more at each side, and 6 points round it. So does each key's icon, the play, back and on keys and the volume's speaker, 6 points round it. The top row is one shadow right across the window, as high as the row: the phone's header, or the desktop's tabs out to the window's keys. Its words and keys have none of their own. A shadow that reaches the window's edge runs on past it, so it doesn't fade there. A calm below 0 makes the colour in a shadow richer as well as darker, up to twice as rich where the shadow is whole. The words are the song's lines, the time and a button's label, wherever the skin puts them; a part's box, however wide, doesn't count, only where its words are drawn. The status bar's marks have grounds of their own and aren't counted, but the modes' shuffle and repeat have none and are, each mark's icon like a key's.- Calm goes under the words and keys unless
hazesayszones all:haze: saturation 15 value 30 calm 75 zones allputs it over the whole page, for a sheet that is words all over, like the queue's list. A list's rows have no calm of their own, so this is the only way to get it under them.zones wordsis the default. Over the whole page it's a veil of one strength with no feather. It isn't the shadow at full strength, because lifting the value does nothing to a colour already at full, and calm 70 under words leaves a full blue as it is. Above 0 the veil mixes every colour that percent of the way to white. Below 0 it darkens and enriches the colour as a shadow does. Calm 75 in the light and -70 in the dark keep black or white ink at 100, 80 and 60% at 4.5 to 1 or better over any cover; a full blue is the worst light one.haze_test.cchecks it. background: hazeon a part, as paradise-phone'smini-playerhas, is the page's own haze with the part'shazenumbers and the calm over all of it, drawn where the part is over$bg; withzones wordssaid the calm is under the part's own words and keys instead, as on a page, paradise-phone's turned mini player. It's the window's size, so it's the same picture a sheet with those numbers has, made once for both, and the part shows the cloud as it is there whatever its size. Where there's no cover to make it from, it's$bg, which a skin can set for the part.- On a hazed page, and in a part with
background: haze, the app sets$text,$soft,$dimand$trackto black in the light and white in the dark, at 100, 80, 60 and 25%, as a rule for the page written before the skin. So a skin that sets those tokens in a rule for the page, or for a part on it, overrides the app's ink, in@darkor not, and its top-level ones don't reach the page. background: backdrop 10 50%is squares of the cover's colours, ten across, at half strength, over$bg. 10 across and full strength unless it says.
skin.pss - Mistakes and Warnings
Reading stops at the first mistake, and nothing of the skin loads. It says the line and what's wrong, and whether the line is the skin's or its variant's. Anything the language doesn't have is a mistake, not ignored: a character it doesn't use, a property, a state, a value a property can't take, a part an area map names that isn't in the part it arranges, a picture that won't load, a skin for another layout.
A warning is something paradise works around: a needed part a page leaves out, a page with no arrangement of its own, a selector that names nothing on any page, a font that isn't there, a token whose value can't be used where it's read, an @skin with no kind, a name in a bracket that comes to 0, text-width in a bracket, icon: $name, a fraction written for a size. skin-check prints them; paradise doesn't stop for them.
skin.pss - Limits
- Blocks nest 32 deep, and so do brackets.
- 256 selectors in one rule, nesting multiplied out, and 32 parts in one selector.
- Numbers up to 1000000000 as written, and sizes come to at most 100000 points either way.
- 2 to 4 colours in a gradient.
- An area map is at most 64 rows of 16.
- Tokens made of tokens go 16 deep.
- 128 skins and variants found, all told.
Variants
A variant is a .pss read after the skin, as if written below it. It lives in the skin's own folder as variants/<id>.pss:
@variant {
name: "Dark";
}
$bg: #16181c;
$text: #eceef1;
now-playing {
haze: saturation 20 value -25 calm -70;
$text: #ffffff;
$soft: #ffffffcc;
}
It names itself in @variant, its name under Theme on the settings page, or its file's name with none, and then sets fonts, tokens and rules. They rank as if written below the skin's: where it says as much it wins, and where the skin says more it doesn't. A skin's page rule beats the variant's global one, and in the dark the skin's @dark beats the variant's plain tokens, so a variant that changes a colour the skin's @dark also sets says what it is in the dark too. It can't hold @skin or change the layout. A skin takes one variant at a time.
in @variant |
|
|---|---|
name: "..." |
Its name. |
for: SKIN... |
The skins it's for, by folder name. One whose for: leaves a skin out isn't offered over it. With none, it's for the skin it's beside. |
The pictures and fonts a variant names are looked for in its own folder first, then the skin's: for a variant in variants/, "dusk/frame.png" is variants/dusk/frame.png if that's there, else the skin's dusk/frame.png.
Under Theme on the settings page a skin's variants follow it, each as Skin: Variant. paradise and paradise-phone are listed as System, Light and Dark instead, with no name before them, except when both are there in Dev mode. paradise remembers which variant each skin was on. A variant shares its skin's window places, so switching between them leaves the window where it is. Variant file names follow the same rule as folder names, and one that doesn't read over its skin is left out, with why in the log.
A variant downloaded from the online catalog can't go in its skin's folder, which can be one paradise ships. It goes in variants/<skin>/ in paradise's data folder, and the skin's variants are read from there too, after its own. One called the same as one of the skin's own is left out. Its files are looked for in that folder first.
A skin with a variant called light and another called dark follows the system's light or dark setting until a variant of it has been picked: it starts in the one the system says, and moves to the other when the system does. Picking any variant of the skin under Theme on the settings page ends that for the skin, and picking the skin as it is, with no variant, lets go of the choice: it follows the system again.
Add-ons
An add-on draws marks into the slots the app has: a row's badge (the library's tree rows and the tracks' rows), an icon's badge (the library's icons), and the status bar's indicator. It can't touch anything else, so one add-on goes on every skin. It's a folder in paradise's data folder, addons/<id>/, holding <id>.pxt and the pictures it names, and paradise puts every one there on whichever skin is up, in order of id. One downloaded from the online catalog goes there, and can be switched off on the plugins page.
@addon {
name: "Album icons";
}
$mark-green: #4cc38a;
library icon badge {
size: 16;
radius: 4;
padding: 2;
icon-size: 12;
}
library icon badge.music:source-local,
now-playing indicator.music:source-local {
icon: "ind_local.svg";
color: $mark-green;
}
- A mark is a slot's name with a kind of the add-on's own:
badge.music,indicator.music. The marks in a slot are the kinds the add-on's rules name for it, in the order first named, across the slotgapapart. - A mark shows where its style has an
icon, and gives its room back where it hasn't;icon: nonehides one. When it shows is its states, which are those of the row, the icon or the track playing it's in:source-local,art-tagsand the rest. - Its rules can say anything a part's look takes. Where a mark goes is its slot's, so
areas,across,down,center,anchor,go,bleedandindentare mistakes, and so are layers and@font. A rule for anything but a slot is a mistake. - Its rules and tokens are read only for its own marks, as if written after the skin and its variant. They rank with the skin's for a mark as a variant's would, so a skin's
now-playing statusbar indicator { color: ... }beats an add-on'sindicator.musicthat doesn't name the page; an add-on that wants its own colour there saysnow-playing indicator.music. A token is the add-on's where it has one, then the skin's. - One with a mistake is left off, with its line in the log and on its row on the plugins page. The skin loads as if it weren't there.
The slots are the skin's to place and style. icon badge { ... } in a skin styles every add-on's marks on an icon, and a skin places badge in a row's or an icon's map, or anchors it. Where it doesn't, the standard look puts an icon's at its cover's top right, 4 in, and a row's at its right end, 8 in, the marks 3 apart.
Built in
paradise (for the desktop) and paradise-phone (for a phone) are in paradise itself: the build bakes their skin.pss, their variants, the SVGs in their icons/ folders and the two Selawik fonts into the exe, so there is nothing to install or lose. paradise starts a new install on the one for the device, in the variant the system says, and falls back to it when the skin it had won't load. They are listed with the skins on disk, after them. A skin in a folder of the same name takes their place, so a copy in paradise's data folder, skins/paradise, is how one is worked on with Dev mode on. Their fonts are fonts/selawik-light.ttf and fonts/selawik-semilight.ttf, and a skin on disk that names them gets them from the exe if it has no file of its own.
The icons in both, and paradise's own icons for a part a skin gives none, are Lucide's (ISC licence: LICENSE-lucide.txt).
License
CC BY-SA 4.0, in LICENSE. Tahoma and Trebuchet MS are Microsoft's and aren't in here.