Files in the top-level directory in any check-in
- base
- common
- override
- skins
- cpanfile
- inskinerator
- LICENSE.md
- README.md
The Fossil Skin Generator
This project generates Fossil skins using SCSS rules to modify a base skin using variable substitutions.
The tool also allows wholesale overrides of base skins using external SCSS files.
Installation
To install the Perl CPAN dependencies from the project’s top level, say:
$ sudo cpanm --installdeps .
The program is made to run from its source tree. You do not “install” it, per se.
Usage
Synopsis
./inskinerator [--base/-b NAME] [--override/-o NAME]
[--define/-D VAR=VALUE] [--late-define/-L VAR=VALUE]
[--output-scss FILENAME] OUTPUT_CSS_FILE_NAME
Options
--base/-b: Give the base skin to generate the output from. Can be anything in the
base/
subdirectory. If not given, uses the Fossil default skin.--override/-o: Override elements of the
--base
skin with files fromoverride/NAME/*.scss
.--define/-D: Define an SCSS variable between the early- and late-defined variables from files on disk. Most useful with values that are used as inputs to late-defined variable definitions.
--late-define/-L: Override SCSS variables after everything else is computed. Primarily intended to overwrite single skin variables, but may also be used for special purposes with custom
--override
skins.--output-scss: Write the generated SCSS intermediary text to a file before attempting to compile it. This is mainly useful in debugging, since the SCSS compiler gives line numbers into this intermediary. It is also helpful in sorting out variable override problems: if you can’t see why your
-D
settings aren’t taking effect or are getting mangled, generate this output file and trace the variable’s use through the SCSS.
Inskinerator writes the resulting CSS to the required output file name
OUTPUT_CSS_FILE_NAME
or to stdout if you pass “-
” instead.
Early-Defined Variables
Most of the time, a given “base” skin will let you choose to overwrite a
small set of variables via --define/-D
, giving a large number of
different outputs from those few input choices. This explains the
variety of output skins from our few simple examples
below.
There are four of these that we consider “key” variables across skins:
Variable Name | Which controls... |
---|---|
text-color |
body text color |
background-color |
page background color |
pri-accent-color |
the skin’s primary “accent” color |
sec-accent-color |
the secondary accent color |
For a comprehensive list of early-defined variables that you can
overwrite with -D
, see the early-vars.scss
files for your chosen
base and optional override skin. For example, if you’re generating your
skin as
$ ./inskinerator -o amber-vt my-skin.css
…the files base/default/early-vars.scss
and
override/amber-vt/early-vars.scss
are loaded before the -D
definitions are inserted into the generated SCSS output, so those are
the most likely targets for -D
flags. Changing any other SCSS variable
via -D
is likely to be ineffective, either because it simply isn’t
used in that processing chain or because it’s overwritten later in the
process.
Late-Defined Variables
It is also possible to overwrite values at the tail end of the process
via --define-late/-L
.
For a comprehensive list of existing late-defined variables, see the
late-vars.scss
files corresponding to early-vars.scss
as in the
prior section. Realize that -L
can also overwrite any
early-defined variable that makes it through to the point where -L
processing occurs.
A number of the late-defined variables are meant as derived values
based on early-defined variables, so you get the most power from
Inskinerator if you leave them unset clear through the process so they
can take on their defaults. The ultimate reference for these is the
late-vars.scss
files here in the source tree, but there are several
that are worth knowing:
Variable Name | Default Late-Defined Value |
---|---|
title-color |
$pri-accent-color |
header-color |
$text-color |
footer-color |
$text-color + 40% lightness |
ter-accent-color |
inverse of $pri-accent-color |
Given a choice, we advise that you prefer using early-defined variables.
Hue Control
I wish there was a knob on the TV so you could turn up the intelligence. They’ve got one marked “brightness,” but it doesn't work.
— Gallagher
The shipping skins vary in how they control the overall color scheme of the skin.
For “base” skins with neutral background and text colors — for instance, the Fossil default, where the background is white and the text is a very dark gray — the skin’s color theme comes from the accent colors. In the default Fossil skin, it is in fact only the primary accent color that adds any color to it, since the secondary accent color is a light shade of gray, another neutral. The only other color in the Fossil default skin is the tertiary accent color, calculated by default as an inverse of the primary.
For skins that start out with an overall color tint such as Amber VT, we generally derive color shades from the background color by preference. If that won’t work for some reason, then the text color, perhaps because the background color is neutral and thus has 0° hue, so all derivatives on the saturation and lightness axes in the HSL system will also be neutrals. We prefer deriving colors from the background color because it dominates the page in a way that the text color usually does not.
This technique of deriving secondary colors from the hue of the background or text color works best if you’re starting from a light background, since Fossil defaults to that. See our “Fire” and “Fizzbuck Dark” examples below for the sort of accommodations needed to get around the consequences of not following this advice.
In skins of this latter sort, the accent colors are often simply that: accents that complement the overall page hue. To speak more on this gets us into color theory, which is a topic best left to experts.
Simple Examples: Adjusting the Default Skin
Example 1: Fossil Default Rebuilt
For comparison to the static stock Fossil skins, it is useful to simply rebuild Fossil’s default skin with Inskinerator:
./inskinerator -b default default-rebuilt.css
The result varies slightly from the actual Fossil default skin because this tool consolidates several nearby colors to remedy a certain lack of discipline in the way colors were assigned in the stock version. For the most part, you aren’t likely to see any of these differences, since they control elements of the UI that are too small for the difference to be readily apparent.
There were cases where two different border colors differed by only a few points on the HSL scale, for example; a slight saturation or lightness shift in a 1px wide border isn’t likely to change the result in a way you can easily see, so we didn’t bother to create two different colors to exactly match the stock skin.
There is one case where it’s pretty obvious, though: the timeline detail boxes in Modern mode: our shipping files give a lighter shade of gray with the above command than Fossil’s default currently uses because we’re reusing a close-enough shade of light gray defined for other purposes rather than create a one-off for this specific part of the skin.
Because -b default
is implicit, we will not give it in subsequent
examples.
Pro tip: Rather than give an output file name, give “-
” and pipe
the output to a program that puts stdin on the operating system’s
clipboard. On macOS, for example:
./inskinerator - | pbcopy
Now you can simply paste Inskinerator’s output into the CSS editor within Fossil’s skin admin tool.
Example 2: Fire
Having proven that we can get nearly-identical results to the static
default Fossil skin out of Inskinerator, let’s create a variation on it
by passing it CSS color names with -D
to create a “fire” themed
variant:
./inskinerator \
-D header-color=yellow \
-D text-color=lightyellow \
-D background-color=black \
-D pri-accent-color=red \
-D sec-accent-color=darkorange \
fire.css
This skin has a few problems out of the box:
Fossil’s defaults assume you’re using a skin with a light-colored background, as that’s not only the default, it’s the most common overall.
There are a few different workarounds for skins like Fire:
Within the Details section of Fossil’s skin admin tool, set the
white-foreground
setting to 1. This is a good choice for skins like Fizzbuck Dark below where the surrounding text is also white, but the Fire skin uses light yellow text, so…Set the skin’s
pikchr-foreground
setting to any of the standard 148 CSS color names like the Fire skin’s “lightyellow
” text color setting or to an RGB triplet in hex form like0x123456
. (To simplify the Pikchr parser, it doesn’t allow CSS-style RGB triplets,#123456
, since#
starts a comment in Pikchr source code.)
If you view the processing model diagram under the Fire skin, you will notice that the diagram matches the surrounding text, because I’ve made this setting change. If I hadn’t done one of these two settings changes, the diagram would be black-on-black.
This method of deriving a skin by overriding only a few variables breaks down in a few places if the new skin is too different from the base skin. Some default colors either aren’t controlled by the skin or they aren’t available to be overridden, so you end up with pages like the “diff” views at the bottom of
/info
pages having light-colored text atop light-colored backgrounds.One solution is to modify the stock skins to expose more of these variables to overwrite, so you can correct each of these problems.
Another is to jump off the Inskinerator train at that point and hand-modify the result to fix up the remaining minor problems.
Finally, you could jump ahead and treat this as a mere prototype for an override skin, which gives you the control you need to fix this without losing the advantage of Inskinerator’s automation.
Example 3: PDP-11/70
Getting back to light-mode themes, you aren’t restricted to only the 148 standard CSS color names. You can also pass in CSS RGB hex strings to create the “PDP-11/70” skin:
./inskinerator \
-D text-color=\#000 \
-D title-color=\#622A5A \
-D header-color=\#B3003B \
-D pri-accent-color=\#B3003B \
-D sec-accent-color=\#AE98CD \
-D ter-accent-color=\#B3003B \
pdp-11-70.css
Note that we did not override the background-color
: the default
skin’s white background is what we want here.
We can’t avoid changing the text color, though, since the default uses
a very dark gray, not black. But note also that we pass such
strings through as-is, so that CSS’s allowance for the #000 shortcut
works. (Or you could just say “black
”, but we’re trying to make a
point about RGB triplets here.)
Incidentally, the backslashes before the #
in the CSS RGB color specs
are there because that character is special to Vim, and we run such
commands through Vim while testing Inskinerator. (It means “previous
file,” which is how the common Vi command :e#
works.) If you’re
running the commands from a POSIX shell, you don’t need to escape it
like this.
Example 4: Mint Green
You may mix CSS color names and RGB specs, which allows you to create a mint green variant of the default skin:
./inskinerator \
-D text-color=\#030 \
-D title-color=green \
-D header-color=darkgreen \
-D background-color=mintcream \
-D pri-accent-color=lightseagreen \
-D sec-accent-color=\#CFE \
-D ter-accent-color=darkgreen \
mint-green.css
Because the text is a deep green, the Pikchr diagram on the repository’s
home page looks a little off with the Pikchr default black lines and
text, so we’ve gone into the skin’s Details section within Fossil Admin
→ Skins and set “pikchr-foreground: 0x003300
”, that being the way to
tell Pikchr the same color as we gave to CSS as #030
. If we had to use
one of the 148 standard named colors that Pikchr understands, we’d have
had to settle for “DarkGreen,” which is close and looks nice, but we
think it’s better if the diagrams match the surrounding text color.
Example 5: Fizzbuck Light
Here’s a simple skin that doesn’t stray very far from the default skin, primarily showing how a few overrides can create a rather different feel, Fizzbuck Light:
./inskinerator \
-D text-color=black \
-D pri-accent-color=\#3B579A \
-D sec-accent-color=lightgray \
fizzbuck-light.css
Example 6: Fizzbuck Dark
If you invert that light skin by making the accent color the background color then make a few necessary accommodations for that change, you get Fizzbuck Dark:
./inskinerator \
-D text-color=white \
-D background-color=\#3B579A \
-D pri-accent-color=lightgray \
-D sec-accent-color=slategray \
fizzbuck-dark.css
As discussed above, you need to go into the skin’s Details
section and set white-foreground
to get a useful result.
Advanced Examples: Applying Skin Overrides
While the Fossil Default skin derivatives in the prior section were mainly generated by overriding the four key colors, there’s a more powerful alternative within Inskinerator: apply entire SCSS files atop the base skin.
Example 7: Amber VT
This project ships with two such overrides. We’ll get to the second later, but let’s demo one of them now, the Amber VT skin from the PiDP-8/I project. Rebuild it atop the default skin like so:
./inskinerator -o amber-vt amber-vt.css
Example 8: Green Bar VT
You can define background-pattern
as any CSS color function to
override the default solid background set via via background-color
,
producing the Green Bar VT skin:
./inskinerator -o amber-vt \
-D background-color=\#B8F0A8 \
-D background-pattern='repeating-linear-gradient(#EEFAEB, #EEFAEB 50px, white 0px, white 100px)' \
-D vt-text-color=lime \
green-bar-vt.css
Amber VT is one of the skins in which the primary hue control is
the background-color
setting: from that it derives the colors of the
page title, the body headers, the main menu bar, and even the body text.
Because of the latter, early-defining text-color
has no effect when
basing a skin on Amber VT; you have to late-define it to overwrite the
generated value as we’ll do in the next example.
This skin demonstrates a key point: you must also provide
--background-color
when basing a skin on one that derives colors from
it when giving --background-pattern
because even though this won’t set
the skin’s background color per se, SCSS isn’t smart enough to derive
solid colors from a CSS function like repeating-linear-gradient
.
The common situation is that you give one of the key colors in the
background pattern as the solid color to derive the other colors in the
skin from. However, there’s nothing to enforce this. Play around with
making background-color
a shade of blue, for instance.
Example 9: Paper White VT
We can now come nearly full circle and create a Paper White VT skin with a color scheme reminiscent of the default Fossil skin but with all the features of the Amber VT skin:
./inskinerator -o amber-vt \
-D background-color=white \
-D vt-background-color=\#333 \
-D vt-bezel-color=darkgray \
-D vt-text-color=\#EEE \
-L header-color=\#444 \
-L pri-accent-color=\#4082C4 \
-L ter-accent-color=\#4082C4 \
-L text-color=\#444 \
-L title-color=\#4082C4 \
paper-white-vt.css
Example 10: Fossil Modern Default Override
…And we can complete the journey by using the other override skin shipped with Inskinerator, Modern:
./inskinerator -o modern modern-default.css
The result is the core of the Amber VT override with all of the
PDP-8/I retrocomputing styling extracted. You can use this
as a standalone override, as you see here, or you can use SCSS
to @include
it into another override as a base for further
enhancement, as Amber VT does.
You may notice that the Pikchr diagram font size for this skin is a bit
smaller than that of the surrounding body text. This is because the
Fossil default skin uses a smaller-than-normal font size of 0.8 em, and
one of the things this override changes about it is to return the body
font size to its HTML default, “medium”, which is 15% larger in local
tests. The problem is, Pikchr must estimate the size of text-containing
graphical elements based on assumptions about the font metrics, because
there’s no mechanism in SVG to say things like “make this box 20% wider
than this text string.” In lieu of that, Pikchr assumes you’re using
something like Helvetica or Arial as the body font and that you want the
text the same size as the Fossil default skin’s body text when
estimating the size of graphical elements dependent on the text size,
such as “box
” elements containing labels.
There are two ways for us to compensate:
For Pikchr diagrams only, decrease the font size to approximate the Fossil default skin’s body text.
Require that you set the
pikchr-scale
value in the Fossil skin’s Details section to 1.15, that being the Modern override’s 15% font size increase over the default skin.
Since Inskinerator produces only Fossil skin CSS, not any of the other
elements of the Fossil skin, we choose the first option. If you want
your Pikchrs to use the same font size as your body text without blowing
its sizing estimates, you could override this skin’s font-size
adjustment for .markdown.pikchr-wrapper
, then take option 2.
Processing Model
The tool combines files indirectly named on the command
line with the --base
and --override
options with files from
common/*.scss
and variable overrides via --define
options to
produce its output. The order of operations is fixed so that the
resulting CSS rules cascade as expected:
CV: box "common vars" "(early)" arrow 50% box dotted "base vars" "(early)" arrow same box dotted "override vars" "(early)" arrow same DV: box dotted "-D vars" "(early)" LO: box dotted "override vars" "(late)" at 0.8 below CV arrow 50% box dotted "base vars" "(late)" arrow same box "common vars" "(late)" arrow same LV: box dotted "-L vars" "(late)" CC: box "common" "CSS" at 0.8 below LO arrow 50% box "base" "CSS" arrow same box dotted "override" "CSS" arrow same OP: box dotted "override" "Prism.js" BM: box dotted "base" "@media" at 0.8 below CC arrow 50% OM: box dotted "override" "@media" arrow right 0.2 from DV.e \ then down 0.4 \ then left to LO.w-(0.2,-0.4) \ then down to 0.2 left of LO.w \ then to LO.w radius 0.1 arrow right 0.2 from LV.e \ then down 0.4 \ then left to CC.w-(0.2,-0.4) \ then down to 0.2 left of CC.w \ then to CC.w radius 0.1 arrow right 0.2 from OP.e \ then down 0.4 \ then left to BM.w-(0.2,-0.4) \ then down to 0.2 left of BM.w \ then to BM.w radius 0.1
(Boxes with dotted outlines mark elements that are simply skipped if missing. The exceptions are files provided with Inskinerator, so they’re always part of the processing chain.)
That is, Inskinerator:
Concatenates the “early” SCSS variables from up to 4 different sources in order of most generic to most specific to the output skin. These come from
early-vars.scss
files for the first three steps: the ones in./common/
, in./base/NAME/
, and in./override/NAME
, respectively. (See the program usage summary for how theseNAMEs
get specified.) Those plus any values defined on the command line via--define/D
are meant to contain only the initial constants that affect the overall skin, no values calculated from them.Concatenates the “late” SCSS variables in nearly the opposite order.
This strange ordering allows the base and override skins’ late-defined variables to use your early-defined variables (
--define/-D
) in functions that derive other variables, so you only have to provide a small number of inputs to drive an entire skin’s definition. The near reversal of element order allows the base and common late-defined variables to use late-defined values from the override; it also allows the override to change the way those lower levels calculate their derived values by replacing!default
definitions in those lower levels.We don’t completely reverse the order of elements on that second row of the diagram because we want
--define-late/-L
to be a static overwriting mechanism at the very end of the variable chain. Also, if we applied-L
before the override’s late-defined vars, that would put-L
processing immediately after-D
, so there would be no useful distinction between them.You almost always want to use
-D
rather than-L
, since that allows those changes to affect later calculations: one-off last-minute changes via-L
should be rare.Concatenates the specified
css.scss
files in a predictable order. If you’re using an--override
skin, you have the option to include aprism.scss
file to add output from Prism.js’s skin generator from a separate file, which can therefore be more easily maintained than if you mixed it with your main rules inoverride/NAME/css.scss
.Appends the specified
media.scss
files in a predictable order. This split of main CSS from@media
CSS allows the latter to override all prior main CSS rules by collecting all@media
rules near the end of the file.
Making New Skins
The core rule when creating new skins using Inskinerator is to never
modify any of the stock SCSS files. Either overwrite the default values
of variables defined in those files via -D/L
or create a new skin
under override/
and apply it to one of the base/*
skins with -b/o
.
We recommend that you study one of the override skins — e.g. Modern —
for style advice. You should not take style cues from the SCSS files in
base/
or common/
because the cuter we get in
making use of SCSS power in these files, the harder we make merging in
upstream changes. Overrides like Amber VT are free to use the
full power of SCSS as understood by libsass — upon which Inskinerator
is based — so they properly show off the system’s power.
The Core Philosophical Idea: Less Is More
We want you to be able to derive new skins from existing ones with
Inskinerator by changing just a few key variables. All else is to be
derived from those values. One-off values are a problem because each one
may require another -D/L
flag when making a new derivative skin.
Not only is it more complicated if your derived skin requires too many variable overrides, it requires uncommon skill to avoid creating ugly skins when using too many colors. If you study products of professional graphic design, they tend to use four colors per finished deliverable at most: the base background/paper color, the body text color, and an accent color; if the designer is getting really fancy, they’ll also use a secondary accent color. You generally find exceptions to this only in marketing material, advertising, entertainment media, etc. Since you would not expect to find Fossil powering such a web site directly, it’s a good rule of thumb that Fossil skins should not be designed according to the same principles.
For a Fossil skin, we generally prefer readable over eye-catching, accessible over exciting, and useful over entertaining.
You saw this idea in action in the examples above, and you’ll see it again and again below. Derive new colors from existing colors where possible, and avoid creating one-offs.
This is not just about colors: it can also affect whitespace and other elements of the skin. Adjusting 14 whitespace parameters from one base variable is better than needing to adjust each of the 14 parameters independently.
This is not to say that it’s wrong if your derived skin needs, say, 8
-D/L
flags to create the look you want. We hope, however, that only
four of those get you at least 95% of the way to your desired end, and
that the rest are for dialing in small details.
Converting Existing Skins to Inskinerator
When you cannot derive your desired skin from base or override skins shipped with Inskinerator, the next-easiest path is to convert your existing Fossil skin wholesale. This primarily involves replacing explicit CSS with SCSS variables so you can create new derivative skins.
The first step, therefore, is to learn SCSS if you don’t already know it. This CSS-derived language gives it features like variables, conditionals, functions, and more.
The primary implementation choice is how you’re going to handle
hue control: will your new base skin be accent color driven,
background color driven, or text color driven? That’s not only a
personal design choice, it will also be affected by the nature of the
skin. You may want to study the Fossil default skin, which is accent-driven:
all of the blue colors in the output are derived from
pri-accent-color
, and most of the grays are derived from
sec-accent-color
. There are also a few cases where it derives an
alternate text color from text-color
.
Contrast Amber VT, where most of the browns are derived from the background color, except for the more reddish browns which are derived from the primary accent color. The secondary accent color is hardly used.
Disentangling a Manually-Derived Skin as an Override
You may have taken one of the stock Fossil skins and manually hacked it up to produce a custom skin. For such a case, it is better if you don’t create a new Inskinerator base skin from it directly, but to first disentangle your personal changes, save that out as the start of a new override, then convert the existing Fossil stock skin to a new base skin along the lines of the examples we currently ship. Then apply your override to the new base skin as we do with our Amber VT example above.
Fossil’s CSS diffing features make this tedious rather than actually difficult: just open two text editors, one containing your current custom CSS, the other empty, and move elements of the CSS over to the second editor as dictated by the diff. For example, you may have changed the base skin’s hyperlink color like so:
a {
- color: #4183C4;
+ color: #98510b;
text-decoration: none;
}
You would restore the base skin to its original state:
a {
color: #4183C4;
text-decoration: none;
}
…then put the customization in the override/NAME/css.scss
file:
a {
color: #98510b;
}
When Inskinerator concatenates these two files, the override leaves the
text-decoration
style alone and changes only the link color.
This makes tracking upstream skin changes easier on yourself. Indeed, if you contribute the conversion of the base skin to the Inskinerator project, your humble maintainer might just track those changes for you, leaving you to maintain only your override.
You could then go the extra mile, adding variables to your override so you can create customized versions of your override:
a {
color: $link-base-color;
}
The definition of link-base-color
could go in either the
early- or late-defined variables file, or
you could require that the user always pass it via -D
or -L
to avoid
an SCSS compilation error. As a rule, you put hard-coded defaults into
the early-vars
file and computations into the late-vars
file. A
sensible “early” definition would be:
$link-base-color: hsl(30, 60, 90);
A good example of a late-defined calculation in a background color driven override would be:
$link-base-color: adjust-color($background-color,
$saturation: +30,
$lightness: -10);
This is how our Green Bar and Paperwhite VT examples
work: the Amber VT override is heavily variable-driven, so a derived
skin can adjust much of it with -D
and -L
variable changes from the
command line.
Multiple Overrides
You may be wondering if you can have multiple --override
flags on the
command line so you can stack two or more overrides. This isn’t needed
because of SCSS’s @import
feature, which lets you textually
include one SCSS file into another.
You can see this in action in the Amber VT override files: each
one @imports
the corresponding file from ../modern
so there is
little repetition between them. The primary exception is where Amber VT
purposefully overwrites a definition from Fossil Modern Default.
This @import
line should be at the top of the early-vars
file but at
the bottom of the late-vars
file. If you think about it from the
perspective of the processing model, this makes sense:
most-generic to most-specific for the early vars, but the opposite for
the file-based late vars.
For the css.scss
and media.scss
files, we suggest that you put the
@import
lines at the top of the child override’s files to follow the
core CSS design principle: later rules cascade from earlier ones.
However, if you know that your child override doesn’t redefine any CSS
from the parent, it doesn’t much matter where you put the @import
line.
Note that paths are relative to Inskinerator’s current working
directory, and that it is assumed that you are running it from the top
of its development tree. This is why Amber VT imports the early-vars
file from Modern as:
@import 'override/modern/early-vars';
rather than as you may expect:
@import '../modern/early-vars';
It may have occurred to you by now that we could have used @import
to
implement the Inskinerator processing model, making the code
a lot simpler. While that is true, it would shift the burden of ordering
elements to you, the user, rather than have the tool do this in a
predictable, well-considered way. Believe it or not, we didn’t come up
with the Inskinerator processing model by accident. 😉
SCSS Dialect
Inskinerator is based on the Perl CPAN module CSS::Sass
, which uses
libsass under the hood. This is the old C Sass implementation, which
is no longer developed. Fortunately, the language became quite
productive by that point, so we feel no particular anxiety from not
using the current replacement, Dart Sass. Indeed, we’re much happier
with our current Perl dependencies than Node + Dart + NPM module sass
.
The main thing to look out for is that the SCSS namespace refactoring
was done after this development fork. With Inskinerator you must use the
old-style function names: adjust-color
instead of color.adjust
, for
example.