Inskinerator

Top-level Files of trunk
Log In

Files in the top-level directory from the latest check-in of branch trunk


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

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:

  1. 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 like 0x123456. (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.

  2. 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:

  1. For Pikchr diagrams only, decrease the font size to approximate the Fossil default skin’s body text.

  2. 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:

common vars (early) base vars (early) override vars (early) -D vars (early) override vars (late) base vars (late) common vars (late) -L vars (late) common CSS base CSS override CSS override Prism.js base @media override @media
fgcolor = LightYellow
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:

  1. 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 these NAMEs 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.

  2. 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.

  3. Concatenates the specified css.scss files in a predictable order. If you’re using an --override skin, you have the option to include a prism.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 in override/NAME/css.scss.

  4. 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.