body,
h1,
h2,
h3,
h4 {
    font-family: 'Inclusive Sans Regular';
    font-weight: 400;
    font-style: normal
}

body,
button,
.pill-button,
h1,
h2,
h3,
h4,
th {
    font-style: normal
}

.bullet-point,
.clear:after,
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section {
    display: block
}

.font-color,
body,
p a:active {
    color: #333;
}

@media (prefers-color-scheme: dark) {
     .font-color,
    body,
    p a:active {
        color: #eee;
    }   
}

.font-color-dark {
    color: #333;
}

.font-color-light {
    color: #eee;
}

.content,
.tag,
.tag-center,
body {
    background-color: #fefefe
}

@media (prefers-color-scheme: dark) {
    .content,
    .tag,
    .tag-center,
    body {
        background-color: #222;
    }
}

.slider,
header {
    left: 0;
    top: 0;
    right: 0
}

a,
a:active,
a:hover,
blockquote,
body,
dd,
div,
dl,
dt,
fieldset,
form,
h1,
h2,
h3,
h4,
h5,
h6,
input,
li,
ol,
p,
pre,
td,
textarea,
th,
ul {
    margin: 0;
    padding: 0;
    text-decoration: none;
    font-weight: 400
}


.underline,
a:hover {
    text-decoration: underline
}

table {
    border-collapse: collapse;
    border-spacing: 0
}

abbr,
acronym,
fieldset,
img {
    border: 0
}

address,
caption,
cite,
code,
dfn,
em,
strong,
var {
    font-style: normal;
    font-weight: 400
}

ol,
ul {
    list-style: circle;
    margin-left: 15px;
}

caption,
th {
    text-align: left
}

.app-site-title,
.bullet-point,
h1 {
    text-align: center
}

h1,
h2,
h3,
h4,
h5,
h6 {
    font-size: 100%;
    font-weight: 400
}

q:after,
q:before {
    content: ''
}

.clear:after {
    clear: both;
    content: ".";
    height: 0;
    line-height: 0;
    visibility: hidden
}

.button {
    line-height: 26px;
    color: #333;
    background-color:#fefefe;
    height: 31px;
    font-size: 16px;
    margin: 12px 12px 12px 0px;
    padding: 2px 12px 22px;
    border: 2px solid #8c8c8c;
    border-radius: 2px;
}

.pill-button {
    background-color:transparent;
    padding: 4px 0px 2px !important;
    height: auto !important;
    letter-spacing: normal;
    word-spacing: normal;
    line-height: normal;
    text-transform: none;
    text-indent: 0px;
    text-shadow: none;
    /*line-height: 26px;
    color: #333;
    height: 31px;
    font-size: 16px;
    margin: 12px 12px 12px 0px;
    padding: 2px 12px 22px;
    border: 2px solid #8c8c8c;
    border-radius: 2px;*/
}

.button-link,
.button-link:hover {
    color: #333;
}

@media (prefers-color-scheme: dark) {
    .button-link,
    .button-link:hover {
        color: #eee;
    }
}

h3,
p {
    line-height: 26px
}

body,
html {
    height: 100%;
    width: 100%
}

button:focus,
input:focus,
select:focus,
textarea:focus,
.pill-button:focus {
    outline: 0
}

@font-face {
    font-family: 'Inclusive Sans Bold';
    src: url(../fonts/InclusiveSans-Bold.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Bold Italic';
    src: url(../fonts/InclusiveSans-BoldItalic.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Light';
    src: url(../fonts/InclusiveSans-Light.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Light Italic';
    src: url(../fonts/InclusiveSans-LightItalic.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Medium';
    src: url(../fonts/InclusiveSans-Medium.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Medium Italic';
    src: url(../fonts/InclusiveSans-MediumItalic.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Regular';
    src: url(../fonts/InclusiveSans-Regular.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans Regular Italic';
    src: url(../fonts/InclusiveSans-Italic.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans SemiBold';
    src: url(../fonts/InclusiveSans-SemiBold.ttf) format("truetype")
}

@font-face {
    font-family: 'Inclusive Sans SemiBold Italic';
    src: url(../fonts/InclusiveSans-SemiBoldItalic.ttf) format("truetype")
}

body {
    width: auto;
    height: auto
}

/* Regular, not bold, since 2026-08-11. Weight on this site comes from the font FAMILY,
   never from font-weight — the faces are separate families and `font-weight` is pinned to
   400 throughout (see `.bold` at the `.bold, .bold-italic, .italic, .regular` rule).

   The base h1 had been bold, which made every page heading bold whether it asked or not.
   The markup already distinguishes the two cases: nine h1 elements are plain
   `class="left"`, and five opt in explicitly with `class="bold"` — the landing claim, the
   imprint, the privacy policy, the error page and an article title. Those five keep their
   weight, because `.bold` is a class and sits later in this file, so it wins on both
   specificity and order.

   R-037 anticipated this drift: `.header-buttons .pill-current` restates Inclusive Sans
   Bold rather than inheriting it, precisely so a change here cannot move the navigation.
   The current pill is therefore now heavier than the page heading, which is correct — it
   is a marker, not a heading. */
h1 {
    font-family: 'Inclusive Sans Regular';
    font-weight: 400;
    font-size: 27px;
    line-height: 38px
}

h2 {
    font-family: 'Inclusive Sans Regular';
    font-weight: 400;
    font-size: 22px;
    line-height: 32px
}

button,
.pill-button,
h3 {
    font-family: 'Inclusive Sans Regular Italic';
    font-weight: 400;
    font-size: 20px
}

h4 {
    font-family: 'Inclusive Sans Bold Italic';
    font-weight: 700;
    font-size: 18px;
    line-height: 22px;
}

/* The landing-page claim — the h1 of the site root. It has no navigation pill to take
   that role, unlike every section page, so the claim carries it (R-029).

   text-align must be restated: the site centres every h1 through the
   `.app-site-title, .bullet-point, h1` rule, and an element selector on the h1 itself
   beats the alignment inherited from the `.left` wrapper. `inherit` rather than `left`
   so the wrapper stays in charge if it ever changes.

   The decoration around it is a set of four alternatives, one drawn per request in
   index.php and applied through a body class (R-030). All four are CSS only — the site
   runs no JavaScript of its own. */
.claim {
    text-align: inherit;
    font-size: 48px;
    line-height: 58px;
    /* At this size a long word can push past a narrow viewport before the line breaks.
       The claim has no word long enough today, but the rule costs nothing and keeps a
       later rewrite from overflowing the page. */
    overflow-wrap: break-word;
}

/* ── Landing-page effects ─────────────────────────────────────────────────────────
   index.php draws one of four per request and sets `effect-<name>` on the body
   (R-030). All four are CSS only; this site runs no JavaScript of its own.

   Three of them paint into a full-viewport layer behind the content. That layer is
   body::before / ::after at z-index -1 — above the page background, beneath everything
   else. It only works because .content gives up its own background on this page:
   otherwise the opaque content box would cover the layer completely.

   Colour follows one rule throughout, the same one the aberration established: the
   ADDITIVE primaries (red, green, blue — they mix toward white) on the dark scheme, the
   SUBTRACTIVE ones (cyan, magenta, yellow — they mix toward black) on the light scheme.
   Decoration then always resolves toward the text colour instead of away from it
   (R-028). */

body.landing .content {
    background-color: transparent;
}

body.landing::before,
body.landing::after {
    content: "";
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    pointer-events: none;
}

/* 1. Aberration — the three colour channels orbit a common centre at radius 5.6px.
   Six keyframes, one every 60 degrees. Not twelve: between stops the offset cuts the
   chord rather than following the arc, and at 60 degrees that shortcut is 0.75px, which
   nobody can see. At 90 it becomes 1.6px and the rotation starts to visibly pulse.
   The offsets are shared between the two schemes; only the colours differ. */
@keyframes claim-fringe-subtractive {
    0%,
    100% { text-shadow:  5.60px  0.00px 0 rgba(0,255,255,.65), -2.80px  4.85px 0 rgba(255,0,255,.65), -2.80px -4.85px 0 rgba(255,255,0,.65); }
    16.667% { text-shadow:  2.80px  4.85px 0 rgba(0,255,255,.65), -5.60px  0.00px 0 rgba(255,0,255,.65),  2.80px -4.85px 0 rgba(255,255,0,.65); }
    33.333% { text-shadow: -2.80px  4.85px 0 rgba(0,255,255,.65), -2.80px -4.85px 0 rgba(255,0,255,.65),  5.60px  0.00px 0 rgba(255,255,0,.65); }
    50%     { text-shadow: -5.60px  0.00px 0 rgba(0,255,255,.65),  2.80px -4.85px 0 rgba(255,0,255,.65),  2.80px  4.85px 0 rgba(255,255,0,.65); }
    66.667% { text-shadow: -2.80px -4.85px 0 rgba(0,255,255,.65),  5.60px  0.00px 0 rgba(255,0,255,.65), -2.80px  4.85px 0 rgba(255,255,0,.65); }
    83.333% { text-shadow:  2.80px -4.85px 0 rgba(0,255,255,.65),  2.80px  4.85px 0 rgba(255,0,255,.65), -5.60px  0.00px 0 rgba(255,255,0,.65); }
}

@keyframes claim-fringe-additive {
    0%,
    100% { text-shadow:  5.60px  0.00px 0 rgba(255,0,0,.5), -2.80px  4.85px 0 rgba(0,255,0,.5), -2.80px -4.85px 0 rgba(0,0,255,.5); }
    16.667% { text-shadow:  2.80px  4.85px 0 rgba(255,0,0,.5), -5.60px  0.00px 0 rgba(0,255,0,.5),  2.80px -4.85px 0 rgba(0,0,255,.5); }
    33.333% { text-shadow: -2.80px  4.85px 0 rgba(255,0,0,.5), -2.80px -4.85px 0 rgba(0,255,0,.5),  5.60px  0.00px 0 rgba(0,0,255,.5); }
    50%     { text-shadow: -5.60px  0.00px 0 rgba(255,0,0,.5),  2.80px -4.85px 0 rgba(0,255,0,.5),  2.80px  4.85px 0 rgba(0,0,255,.5); }
    66.667% { text-shadow: -2.80px -4.85px 0 rgba(255,0,0,.5),  5.60px  0.00px 0 rgba(0,255,0,.5), -2.80px  4.85px 0 rgba(0,0,255,.5); }
    83.333% { text-shadow:  2.80px -4.85px 0 rgba(255,0,0,.5),  2.80px  4.85px 0 rgba(0,255,0,.5), -5.60px  0.00px 0 rgba(0,0,255,.5); }
}

/* The burst. The claim sits at zero offset — no fringe at all, perfectly legible — and
   the channels snap apart and settle back over about three quarters of a second, like a
   lens hunting for focus and finding it.

   IT IS THE BALL THAT SETS THIS OFF. The period is 7.3s to the millisecond because that
   is the ball's horizontal period, so the headline spikes at the instant the ball strikes
   a side wall. The keyframe percentages look arbitrary — 0.74, 2.466, 4.438 — and are
   simply the same absolute timings the burst always had, re-expressed against 7.3s
   instead of the 9s it ran at before the ball existed. Round them and the burst drifts
   off the impact.

   Side walls only. `text-shadow` is one property, so the claim can carry one burst, and
   the two wall periods are deliberately incommensurate — no single animation fires on
   both. A floor or ceiling still splits the ball; only a side wall carries through to
   the type. That hierarchy is the design, not a shortfall.

   This replaced a continuous rotation, for the reason the shooting star taught: steady
   motion is wallpaper and the eye stops reading it inside ten seconds, while a thing
   that happens occasionally can be waited for and can be missed. It also leaves the
   headline clean for eight seconds in every nine, which the rotation never did.

   The decay is in the numbers: 7.5px, then 4.2, 5.8, 2.6, 1.2, then nothing — an
   overshoot and a bounce rather than a fade, with the angle moving each time so it
   rings down rather than pulsing on one axis. */
@keyframes claim-burst-subtractive {
    0%    { text-shadow: 0 0 0 rgba(0,255,255,.65), 0 0 0 rgba(255,0,255,.65), 0 0 0 rgba(255,255,0,.65); }
    0.74%  { text-shadow:  7.50px  0.00px 0 rgba(0,255,255,.65), -3.75px  6.50px 0 rgba(255,0,255,.65), -3.75px -6.50px 0 rgba(255,255,0,.65); }
    2.466%    { text-shadow:  1.44px  3.95px 0 rgba(0,255,255,.65), -4.14px -0.73px 0 rgba(255,0,255,.65),  2.70px -3.22px 0 rgba(255,255,0,.65); }
    4.438%  { text-shadow: -5.25px  2.45px 0 rgba(0,255,255,.65),  0.51px -5.78px 0 rgba(255,0,255,.65),  4.75px  3.33px 0 rgba(255,255,0,.65); }
    6.411%  { text-shadow: -1.30px -2.25px 0 rgba(0,255,255,.65),  2.60px  0.00px 0 rgba(255,0,255,.65), -1.30px  2.25px 0 rgba(255,255,0,.65); }
    8.384%  { text-shadow:  0.77px -0.92px 0 rgba(0,255,255,.65),  0.41px  1.13px 0 rgba(255,0,255,.65), -1.18px -0.21px 0 rgba(255,255,0,.65); }
    10.479%,
    100%  { text-shadow: 0 0 0 rgba(0,255,255,.65), 0 0 0 rgba(255,0,255,.65), 0 0 0 rgba(255,255,0,.65); }
}

@keyframes claim-burst-additive {
    0%    { text-shadow: 0 0 0 rgba(255,0,0,.5), 0 0 0 rgba(0,255,0,.5), 0 0 0 rgba(0,0,255,.5); }
    0.74%  { text-shadow:  7.50px  0.00px 0 rgba(255,0,0,.5), -3.75px  6.50px 0 rgba(0,255,0,.5), -3.75px -6.50px 0 rgba(0,0,255,.5); }
    2.466%    { text-shadow:  1.44px  3.95px 0 rgba(255,0,0,.5), -4.14px -0.73px 0 rgba(0,255,0,.5),  2.70px -3.22px 0 rgba(0,0,255,.5); }
    4.438%  { text-shadow: -5.25px  2.45px 0 rgba(255,0,0,.5),  0.51px -5.78px 0 rgba(0,255,0,.5),  4.75px  3.33px 0 rgba(0,0,255,.5); }
    6.411%  { text-shadow: -1.30px -2.25px 0 rgba(255,0,0,.5),  2.60px  0.00px 0 rgba(0,255,0,.5), -1.30px  2.25px 0 rgba(0,0,255,.5); }
    8.384%  { text-shadow:  0.77px -0.92px 0 rgba(255,0,0,.5),  0.41px  1.13px 0 rgba(0,255,0,.5), -1.18px -0.21px 0 rgba(0,0,255,.5); }
    10.479%,
    100%  { text-shadow: 0 0 0 rgba(255,0,0,.5), 0 0 0 rgba(0,255,0,.5), 0 0 0 rgba(0,0,255,.5); }
}

/* The vertical walls, on a property of their own. `text-shadow` is already spoken for by
   the burst above, which is tied to the horizontal period — so a floor or ceiling strike
   had nothing left to move and the type only ever flinched at the sides.

   `filter: drop-shadow()` is a separate property and can carry the same three channels on
   the same decay, independently and at the vertical period. It is not merely a duplicate:
   chained drop-shadows each shade the result of the one before, so the split compounds
   very slightly rather than laying three flat copies. At these offsets the difference is
   not nameable on sight, which is the point — a bounce is a bounce whichever wall it was.

   5.1s, exactly the ball's vertical period, for the same reason the burst is 7.3s.

   The zero states carry all three drop-shadows at zero offset rather than `none`: filter
   lists only interpolate when both ends hold the same functions in the same order. */
@keyframes claim-flare-subtractive {
    0%      { filter: drop-shadow(0 0 0 rgba(0,255,255,.6)) drop-shadow(0 0 0 rgba(255,0,255,.6)) drop-shadow(0 0 0 rgba(255,255,0,.6)); }
    1.059%  { filter: drop-shadow(7.5px 0.0px 0 rgba(0,255,255,.6)) drop-shadow(-3.75px 6.5px 0 rgba(255,0,255,.6)) drop-shadow(-3.75px -6.5px 0 rgba(255,255,0,.6)); }
    3.529%  { filter: drop-shadow(1.44px 3.95px 0 rgba(0,255,255,.6)) drop-shadow(-4.14px -0.73px 0 rgba(255,0,255,.6)) drop-shadow(2.7px -3.22px 0 rgba(255,255,0,.6)); }
    6.353%  { filter: drop-shadow(-5.25px 2.45px 0 rgba(0,255,255,.6)) drop-shadow(0.51px -5.78px 0 rgba(255,0,255,.6)) drop-shadow(4.75px 3.33px 0 rgba(255,255,0,.6)); }
    9.176%  { filter: drop-shadow(-1.3px -2.25px 0 rgba(0,255,255,.6)) drop-shadow(2.6px 0.0px 0 rgba(255,0,255,.6)) drop-shadow(-1.3px 2.25px 0 rgba(255,255,0,.6)); }
    12%     { filter: drop-shadow(0.77px -0.92px 0 rgba(0,255,255,.6)) drop-shadow(0.41px 1.13px 0 rgba(255,0,255,.6)) drop-shadow(-1.18px -0.21px 0 rgba(255,255,0,.6)); }
    15%,
    100%  { filter: drop-shadow(0 0 0 rgba(0,255,255,.6)) drop-shadow(0 0 0 rgba(255,0,255,.6)) drop-shadow(0 0 0 rgba(255,255,0,.6)); }
}

@keyframes claim-flare-additive {
    0%      { filter: drop-shadow(0 0 0 rgba(255,0,0,.45)) drop-shadow(0 0 0 rgba(0,255,0,.45)) drop-shadow(0 0 0 rgba(0,0,255,.45)); }
    1.059%  { filter: drop-shadow(7.5px 0.0px 0 rgba(255,0,0,.45)) drop-shadow(-3.75px 6.5px 0 rgba(0,255,0,.45)) drop-shadow(-3.75px -6.5px 0 rgba(0,0,255,.45)); }
    3.529%  { filter: drop-shadow(1.44px 3.95px 0 rgba(255,0,0,.45)) drop-shadow(-4.14px -0.73px 0 rgba(0,255,0,.45)) drop-shadow(2.7px -3.22px 0 rgba(0,0,255,.45)); }
    6.353%  { filter: drop-shadow(-5.25px 2.45px 0 rgba(255,0,0,.45)) drop-shadow(0.51px -5.78px 0 rgba(0,255,0,.45)) drop-shadow(4.75px 3.33px 0 rgba(0,0,255,.45)); }
    9.176%  { filter: drop-shadow(-1.3px -2.25px 0 rgba(255,0,0,.45)) drop-shadow(2.6px 0.0px 0 rgba(0,255,0,.45)) drop-shadow(-1.3px 2.25px 0 rgba(0,0,255,.45)); }
    12%     { filter: drop-shadow(0.77px -0.92px 0 rgba(255,0,0,.45)) drop-shadow(0.41px 1.13px 0 rgba(0,255,0,.45)) drop-shadow(-1.18px -0.21px 0 rgba(0,0,255,.45)); }
    15%,
    100%  { filter: drop-shadow(0 0 0 rgba(255,0,0,.45)) drop-shadow(0 0 0 rgba(0,255,0,.45)) drop-shadow(0 0 0 rgba(0,0,255,.45)); }
}

.effect-aberration .claim {
    animation: claim-burst-subtractive 7.3s linear infinite,
               claim-flare-subtractive 5.1s linear infinite;
}

/* Hovering the headline holds the split open instead of waiting for the next burst —
   the continuous rotation the burst replaced, kept on as the interaction. It is the only
   part of the old cursor tracking that survives without JavaScript: CSS can be told the
   pointer is over an element, never where the pointer is. Touch devices simply never
   reach this rule, which is the right outcome rather than a gap.

   THE BURST AND THE FLARE ARE RESTATED HERE, AND THAT IS THE WHOLE POINT OF THIS RULE.
   Animations are matched by position in the name list, so naming them again at indices 0
   and 1 lets them run straight through the hover untouched. Drop them — declare the
   fringe alone, as this rule did until 2026-08-06 — and both are cancelled on hover and
   created afresh from time zero when the pointer leaves. The ball never stopped, so the
   claim comes back at an arbitrary offset and the burst no longer lands on the impact.
   That is permanent until reload, and it silently voids the reason 7.3s is 7.3s.

   The fringe goes last because it and the burst both write `text-shadow`, and the last
   animation in the list wins the property — which is exactly the intent: hovering holds
   the split open in place of waiting for the next burst. The flare is untouched by that,
   writing `filter`, so vertical bounces still carry through while the pointer is down. */
.effect-aberration .claim:hover {
    animation: claim-burst-subtractive 7.3s linear infinite,
               claim-flare-subtractive 5.1s linear infinite,
               claim-fringe-subtractive 6s linear infinite;
}

@media (prefers-color-scheme: dark) {
    .effect-aberration .claim {
        animation-name: claim-burst-additive, claim-flare-additive;
    }

    /* All three named, in the same order as the shorthand above. A shorter list does not
       leave the rest at their previous values — it truncates the set to its own length,
       so naming the fringe alone here would switch the burst and the flare off outright. */
    .effect-aberration .claim:hover {
        animation-name: claim-burst-additive, claim-flare-additive, claim-fringe-additive;
    }
}

/* The ball. No gravity, no drag, no easing — it crosses at a constant rate and reverses
   the instant it touches a wall, which is what "bouncing off the walls" means before
   physics gets involved.

   Horizontal and vertical are two separate animations on two nested elements, because
   an element has one transform and these are genuinely independent bounces. `alternate`
   supplies the reflection: the animation runs to the far wall, then runs backwards.
   `linear` supplies the absence of gravity — any easing at all would have it slow as it
   approached a wall, which reads as weight.

   The two periods are deliberately incommensurate. 7.3 and 5.1 seconds never come back
   into phase at any point a reader would sit through, so the ball keeps finding new
   paths across the page instead of retracing one. Give them a common factor and it
   settles into a closed loop within a minute. */
.effect-aberration {
    --ball: rgba(51, 51, 51, .5);
    --ball-1: rgba(0, 255, 255, .5);
    --ball-2: rgba(255, 0, 255, .5);
    --ball-3: rgba(255, 255, 0, .5);
}

@media (prefers-color-scheme: dark) {
    .effect-aberration {
        --ball: rgba(238, 238, 238, .55);
        --ball-1: rgba(255, 0, 0, .45);
        --ball-2: rgba(0, 255, 0, .45);
        --ball-3: rgba(0, 0, 255, .45);
    }
}

.ball i {
    position: absolute;
    top: 0;
    left: 0;
    width: 14px;
    height: 14px;
    animation: ball-x 7.3s linear infinite alternate;
}

/* Middle: the vertical travel, and the split that a side wall produces. It carries no
   fill — only its shadow shows — but it sits at the ball's true position, which the
   outer element does not, having no vertical travel of its own. That is the entire
   reason this layer exists. */
.ball i i {
    display: block;
    position: static;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    animation: ball-y 5.1s linear infinite alternate,
               ball-split-x 7.3s linear infinite;
}

/* Inner: the ball itself, and the split a floor or ceiling produces. */
.ball i i i {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: var(--ball);
    animation: ball-split-y 5.1s linear infinite;
}

/* Travel stops one ball short of the far edge, so it touches the wall rather than
   halfway leaving through it. */
@keyframes ball-x {
    from { transform: translateX(0); }
    to   { transform: translateX(calc(100vw - 14px)); }
}

@keyframes ball-y {
    from { transform: translateY(0); }
    to   { transform: translateY(calc(100vh - 14px)); }
}

/* The split is what an impact looks like. At rest the ball is a plain disc with no
   fringe at all; the moment it strikes a wall the three channels jump apart, and over
   about a second they settle back into one shape. It is the only cue that a bounce
   happened, so it replaced an earlier opacity dip rather than joining it — two cues for
   one event just muddied both.

   THESE TWO ARE NOT `alternate`, while the travel animations beside them are. That looks
   like an oversight and is the whole trick. Under `alternate` the timeline is a mirror:
   the value on the approach to 100% is the same value on the way back from it, so a ramp
   that gives the split *after* the far wall unavoidably gives it *before* as well, and
   the ball anticipates its own bounce. Running forward only, the loop boundary does the
   work — 100% holds zero, 0% is full split, and the wrap between them is instantaneous.
   A split that appears in no time and fades over a second is what an impact looks like.

   Alignment is exact and not a coincidence: travel is `alternate` over period P, so a
   wall is struck at 0, P, 2P …; the split runs forward over the same P, so it restarts on
   every one of them. Change one period without the other and the ball starts flashing in
   open space.

   Three channels at 120°, radius 5px: (5, 0), (−2.5, 4.33), (−2.5, −4.33). Every keyframe
   carries three shadows in the same order and colours, which is what lets box-shadow
   interpolate at all. */
@keyframes ball-split-x {
    0%   { box-shadow: 5px 0 0 var(--ball-1), -2.5px 4.33px 0 var(--ball-2), -2.5px -4.33px 0 var(--ball-3); }
    14%  { box-shadow: 0 0 0 var(--ball-1), 0 0 0 var(--ball-2), 0 0 0 var(--ball-3); }
    100% { box-shadow: 0 0 0 var(--ball-1), 0 0 0 var(--ball-2), 0 0 0 var(--ball-3); }
}

@keyframes ball-split-y {
    0%   { box-shadow: 5px 0 0 var(--ball-1), -2.5px 4.33px 0 var(--ball-2), -2.5px -4.33px 0 var(--ball-3); }
    20%  { box-shadow: 0 0 0 var(--ball-1), 0 0 0 var(--ball-2), 0 0 0 var(--ball-3); }
    100% { box-shadow: 0 0 0 var(--ball-1), 0 0 0 var(--ball-2), 0 0 0 var(--ball-3); }
}

/* 2. Starfield — dots as tiny radial gradients on tiles of mismatched sizes, so the
   pattern's true repeat is their lowest common multiple and never lands on screen. The
   ::before layer drifts, the sparser ::after layer twinkles. On the light scheme the
   sky inverts to ink specks on paper, which is the same picture as a negative. */
/* The sky itself. Set on the body so it propagates to the canvas and paints behind the
   star layers, which sit at z-index -1 — put it on any of those layers instead and it
   would cover the ones drawn before it.

   Colours are a moonless sky far from a town. It is not black: airglow and unresolved
   starlight leave it a very dark blue, darkest overhead and lifting slightly toward the
   horizon. Deliberately NO warm tone anywhere down there — that orange band is
   sodium streetlight, the one thing a dark sky does not have. The faint teal along the
   bottom is airglow, which is oxygen emission and genuinely that colour.

   Darker than the site's usual #222 on purpose: stars cannot read against mid-grey. */
/* Every star is a core plus a halo, never a hard-edged dot. A 1px disc with a clean edge
   reads as a dead pixel or a speck of dust — the falloff is what makes the eye accept it
   as a light source rather than a mark on the screen.

   The light scheme needs both a bigger radius and a stronger halo than the dark one.
   Against near-white there is far less contrast to work with, so the same geometry that
   reads as a star on black reads as dirt on paper. Size and softness carry it instead. */
.effect-starfield {
    --star: rgba(38, 44, 74, .72);
    --star-glow: rgba(38, 44, 74, .16);
    --star-r: 2.7px;
    /* Light scheme keeps the negative — ink specks on paper — so the sky inverts to a
       cool paper gradation rather than turning into night. */
    background-image: linear-gradient(180deg, #fdfdfe 0%, #f3f5f9 62%, #ecf0f6 100%);
    background-attachment: fixed;
}

@media (prefers-color-scheme: dark) {
    .effect-starfield {
        --star: rgba(255, 255, 255, .95);
        /* Faintly blue, which is what atmospheric scattering does to a point source and
           what keeps the bloom from looking like a grey smudge. */
        --star-glow: rgba(178, 204, 255, .26);
        --star-r: 1.5px;
        background-image:
            radial-gradient(150% 55% at 50% 108%, rgba(28, 60, 56, .45), transparent 72%),
            linear-gradient(180deg, #070c17 0%, #0b1220 40%, #101a2b 74%, #16202f 100%);
    }
}

.effect-starfield::before {
    background-image:
        radial-gradient(circle calc(var(--star-r) * 1.15) at 18% 24%, var(--star) 34%, var(--star-glow) 58%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 0.85) at 62% 71%, var(--star) 34%, var(--star-glow) 58%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 1.30) at 84% 38%, var(--star) 34%, var(--star-glow) 58%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 0.80) at 33% 88%, var(--star) 34%, var(--star-glow) 58%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 1.00) at 47% 12%, var(--star) 34%, var(--star-glow) 58%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 0.70) at  9% 62%, var(--star) 34%, var(--star-glow) 58%, transparent 100%);
    background-size: 260px 260px, 380px 380px, 520px 520px, 300px 300px, 440px 440px, 610px 610px;
    animation: starfield-drift 150s linear infinite;
}

/* Each layer travels exactly one tile, so the loop is seamless — and because the tiles
   differ in size they travel at different speeds, which reads as depth. */
@keyframes starfield-drift {
    to {
        background-position: 0 260px, 0 380px, 0 520px, 0 300px, 0 440px, 0 610px;
    }
}

.effect-starfield::after {
    background-image:
        radial-gradient(circle calc(var(--star-r) * 1.35) at 71% 18%, var(--star) 32%, var(--star-glow) 56%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 1.55) at 28% 54%, var(--star) 32%, var(--star-glow) 56%, transparent 100%),
        radial-gradient(circle calc(var(--star-r) * 1.20) at 88% 79%, var(--star) 32%, var(--star-glow) 56%, transparent 100%);
    background-size: 700px 700px, 900px 900px, 800px 800px;
    animation: starfield-twinkle 6.5s ease-in-out infinite alternate;
}

@keyframes starfield-twinkle {
    from { opacity: .2; }
    to   { opacity: 1; }
}

/* Things that cross the sky. One shooting star and three satellites, in a container
   index.php emits only for this effect — pseudo-elements ran out, and four moving objects
   want four real elements.

   Shooting star and satellite are deliberately nothing alike, which is the whole reason
   for having both: a second of bright streak against a bare dot that takes forty seconds
   to cross and never changes speed. Told apart at a glance, the way they are in life.

   Every path is `rotate(θ) translateX(d)` — rotate first, then travel along the element's
   OWN x axis. That matters more than it looks. Travelling by an (x, y) vector means the
   trail's tilt and the direction of flight are two separate numbers that have to be kept
   in agreement by hand, and they silently disagree the moment the window changes shape.
   Rotating first makes them the same number: whatever θ is, the streak lies along the
   line it flies, at every viewport, with no arithmetic to get wrong. */
.sky,
.film,
.sonar,
.draft,
.ball {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
}

/* Every moving object in every effect. `display: block` is load-bearing — <i> is inline
   by default, and an inline box with no content collapses to nothing no matter what
   width and height say. */
.sky i,
.film i,
.sonar i,
.draft i {
    position: absolute;
    display: block;
    opacity: 0;
}

.sky i {
    /* The head of the streak, so the tail stretches back from a fixed point rather than
       the whole object growing from its middle. */
    transform-origin: 100% 50%;
}

/* The shooting star. Its bearing and entry point come from the sky-* class, which
   index.php draws per request — so it does not always fall the same way. */
.sky i:nth-child(1) {
    left: var(--sx);
    top: var(--sy);
    width: 170px;
    height: 1.5px;
    border-radius: 2px;
    background-image: linear-gradient(90deg, transparent, var(--star));
    animation: shooting-star 19s ease-in infinite;
}

@keyframes shooting-star {
    0%   { opacity: 0; transform: rotate(var(--dir)) translateX(0) scaleX(.3); }
    1%   { opacity: 1; }
    6%   { opacity: 1; }
    8%   { opacity: 0; transform: rotate(var(--dir)) translateX(150vmax) scaleX(1); }
    100% { opacity: 0; transform: rotate(var(--dir)) translateX(150vmax) scaleX(1); }
}

/* Two of the four bearings run right to left, entering past the far edge. Angles beyond
   90 degrees turn the element around with the path, so the head still leads. */
.sky-a { --dir:  18deg; --sx: -14%; --sy:  6%; }
.sky-b { --dir:  37deg; --sx: -10%; --sy:  2%; }
.sky-c { --dir: 156deg; --sx: 112%; --sy:  5%; }
.sky-d { --dir: 139deg; --sx: 108%; --sy:  1%; }

/* The satellites. `linear` is doing real work: any easing at all would give away that
   this is an animation rather than an orbit. Their periods share no common factor, so
   they never fall into step, and the negative delays mean the sky is already busy when
   the page opens instead of empty for the first half-minute. */
.sky i:nth-child(n+2) {
    width: 2.5px;
    height: 2.5px;
    border-radius: 50%;
    background-color: var(--star);
    animation-name: satellite;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

.sky i:nth-child(2) { left:  -2%; top: 13%; --dir:  11deg; animation-duration: 46s; animation-delay:  -8s; }
.sky i:nth-child(3) { left:  -2%; top: 61%; --dir:  -9deg; animation-duration: 67s; animation-delay: -31s; }
.sky i:nth-child(4) { left: 102%; top: 34%; --dir: 169deg; animation-duration: 83s; animation-delay: -55s; }

@keyframes satellite {
    0%   { opacity: 0;   transform: rotate(var(--dir)) translateX(0); }
    3%   { opacity: .85; }
    44%  { opacity: .85; }
    47%  { opacity: 0;   transform: rotate(var(--dir)) translateX(135vmax); }
    100% { opacity: 0;   transform: rotate(var(--dir)) translateX(135vmax); }
}

/* 3. Aurora — three broad colour fields, heavily blurred, turning slowly about the
   centre. The layer is oversized by half the viewport in every direction so the
   rotation never swings an edge into view. */
.effect-aurora {
    --aur-1: rgba(0, 210, 255, .40);
    --aur-2: rgba(255, 0, 200, .34);
    --aur-3: rgba(255, 215, 0, .34);
}

@media (prefers-color-scheme: dark) {
    .effect-aurora {
        --aur-1: rgba(255, 0, 60, .42);
        --aur-2: rgba(0, 255, 120, .32);
        --aur-3: rgba(60, 80, 255, .46);
    }
}

.effect-aurora::before {
    top: -50%;
    right: -50%;
    bottom: -50%;
    left: -50%;
    background-image:
        radial-gradient(circle at 32% 34%, var(--aur-1), transparent 55%),
        radial-gradient(circle at 70% 42%, var(--aur-2), transparent 55%),
        radial-gradient(circle at 48% 72%, var(--aur-3), transparent 55%);
    filter: blur(60px);
    animation: aurora-swirl 34s linear infinite;
}

@keyframes aurora-swirl {
    from { transform: rotate(0deg)   scale(1); }
    50%  { transform: rotate(180deg) scale(1.25); }
    to   { transform: rotate(360deg) scale(1); }
}

/* 4. Grain — film grain, for a studio whose photography is shot on instant film. The
   noise is an inline SVG feTurbulence rather than an image file, so it costs no request
   and cannot 404. Greyscale on purpose: over a light page only its dark half registers,
   over a dark page only its light half, so one texture serves both schemes.

   The jump is per keyframe, not smooth — `steps(1)` holds each position for its whole
   segment, which is what makes it read as grain rather than as a sliding texture. */
.effect-grain::before {
    /* Oversized well past any shift below, so an edge can never travel into view. */
    top: -20%;
    right: -20%;
    bottom: -20%;
    left: -20%;
    /* feTurbulence alone is a near-uniform mid-grey — laid over the page at any useful
       opacity it reads as a grey wash rather than as grain. The feColorMatrix fixes
       that: it forces RGB to black and derives ALPHA from the noise's red channel minus
       a threshold, so a little over half of every tile is fully transparent and the rest
       ramps up. What lands on the page is sparse specks, not a veil. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1 0 0 0 -0.45'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
    opacity: .55;
    animation: grain-shift .5s steps(1) infinite;
}

/* Inverting keeps the alpha and flips the specks from black to white, which is the
   difference between grain on paper and grain on film. */
@media (prefers-color-scheme: dark) {
    .effect-grain::before {
        filter: invert(1);
        opacity: .4;
    }
}

/* Shifts are in pixels, not percentages. The tile is 200px, so a handful of pixels is
   enough to resample it into fresh grain, while a percentage of a layer this oversized
   would move it far enough to read as a sliding slab. */
@keyframes grain-shift {
    0%   { transform: translate(0, 0); }
    20%  { transform: translate(-7px, -5px); }
    40%  { transform: translate(5px, -9px); }
    60%  { transform: translate(-9px, 7px); }
    80%  { transform: translate(7px, 5px); }
    100% { transform: translate(0, 0); }
}

/* The two things that happen to film. A light leak is the rare beautiful accident, once
   in about half a minute; a scratch is the frequent small one, every eleven seconds and
   gone in half of one. Between them the grain gets an event to wait for, the way the
   night sky has its shooting star.

   The leak stays warm in both schemes rather than following the additive/subtractive
   rule the other effects obey. That rule exists so decoration resolves toward the text
   colour — but a leak is light spilling onto the emulsion before the picture was ever
   developed, and it is orange because film is orange there. Turning it cyan on the light
   scheme would be consistent and wrong. */
.effect-grain {
    --scratch: rgba(28, 24, 18, .55);
}

@media (prefers-color-scheme: dark) {
    .effect-grain {
        --scratch: rgba(255, 246, 232, .6);
    }
}

.film-a { --leak-x:  0%;  --leak-y: 38%; }
.film-b { --leak-x: 100%; --leak-y: 55%; }
.film-c { --leak-x: 88%;  --leak-y:  0%; }
.film-d { --leak-x: 10%;  --leak-y: 100%; }


/* The leak. Oversized and heavily blurred, so what reaches the page is a glow with no
   describable edge — the moment you can see where it stops, it stops being light. */
.film i:nth-child(1) {
    top: -25%;
    right: -25%;
    bottom: -25%;
    left: -25%;
    background-image: radial-gradient(ellipse 55% 85% at var(--leak-x) var(--leak-y),
        rgba(255, 176, 74, .9),
        rgba(255, 94, 46, .5) 38%,
        transparent 70%);
    filter: blur(45px);
    animation: light-leak 34s ease-in-out infinite;
}

@keyframes light-leak {
    0%   { opacity: 0;   transform: scale(1); }
    7%   { opacity: .52; transform: scale(1.07); }
    14%  { opacity: .46; }
    24%  { opacity: 0;   transform: scale(1.14); }
    100% { opacity: 0;   transform: scale(1); }
}

/* Held back on the light scheme: the page is already near-white, and the same opacity
   that glows against #222 just turns paper orange. */
@media (prefers-color-scheme: light) {
    .film i:nth-child(1) {
        animation-name: light-leak-pale;
    }
}

@keyframes light-leak-pale {
    0%   { opacity: 0;   transform: scale(1); }
    7%   { opacity: .34; transform: scale(1.07); }
    14%  { opacity: .3;  }
    24%  { opacity: 0;   transform: scale(1.14); }
    100% { opacity: 0;   transform: scale(1); }
}

/* The scratch. `steps(1)` again — it must jitter between positions, not slide between
   them, because a scratch is a physical gouge that the film jumps around, not something
   that travels. Overhangs top and bottom so neither end is ever seen. */
.film i:nth-child(2) {
    top: -5%;
    bottom: -5%;
    left: 34%;
    width: 1px;
    background-image: linear-gradient(180deg,
        transparent,
        var(--scratch) 14%,
        var(--scratch) 86%,
        transparent);
    animation: film-scratch 11s steps(1) infinite;
}

@keyframes film-scratch {
    0%   { opacity: 0;   transform: translateX(0); }
    1%   { opacity: .55; transform: translateX(0); }
    2%   { opacity: .3;  transform: translateX(3px); }
    3.2% { opacity: .6;  transform: translateX(-2px); }
    4.4% { opacity: .28; transform: translateX(4px); }
    5.5% { opacity: 0;   transform: translateX(4px); }
    100% { opacity: 0;   transform: translateX(4px); }
}

/* 5. Sonar — an instrument rather than weather, and the only effect that depicts a thing
   actively looking for something. Range rings sit still, a beam comes round every six
   seconds, and returns bloom where it has just passed.

   The blips run on 6, 12 and 18 second periods — exact multiples of the sweep — so each
   lights roughly under the beam without anything coordinating them. Not perfect sync,
   and it should not be: a return that landed on the beam to the millisecond every time
   would read as a loop, where a near miss reads as a machine. */
.effect-sonar {
    --sonar-line: rgba(24, 68, 92, .20);
    --sonar-beam: rgba(0, 150, 170, .16);
    --sonar-blip: rgba(0, 148, 168, .9);
}

@media (prefers-color-scheme: dark) {
    .effect-sonar {
        --sonar-line: rgba(126, 224, 212, .13);
        --sonar-beam: rgba(130, 255, 225, .12);
        --sonar-blip: rgba(140, 255, 228, .92);
    }
}

/* The origin sits inside the viewport, not in a corner. A corner pivot can only sweep
   the one quadrant that is on screen, so the beam has to jump back after a quarter turn
   — which reads as the animation restarting rather than as a thing going round. Put the
   pivot on the page and a full 360° turn is continuous, with no seam anywhere in it.

   Kept right of centre so the sweep passes behind the claim rather than under it. */
.effect-sonar {
    --sonar-x: 68%;
    --sonar-y: 55%;
    --sonar-period: 9s;
}

/* Range rings, concentric on that origin. `repeating-radial-gradient` draws the whole
   set from one declaration — a 1px band every 9vmin. */
.effect-sonar::before {
    background-image: repeating-radial-gradient(circle at var(--sonar-x) var(--sonar-y),
        transparent 0 9vmin,
        var(--sonar-line) 9vmin calc(9vmin + 1px));
}

/* The beam. An oversized square centred on the origin and turned about its own middle,
   carrying a conic wedge that is brightest at its leading edge and fades away behind —
   so the glow trails the line, the way a display holds phosphor for a moment. Get this
   backwards and the glow arrives first and the line snaps off, which looks wrong without
   being easy to name. */
.sonar i:nth-child(1) {
    left: var(--sonar-x);
    top: var(--sonar-y);
    width: 300vmax;
    height: 300vmax;
    margin-left: -150vmax;
    margin-top: -150vmax;
    opacity: 1;
    transform-origin: 50% 50%;
    /* The third stop is not optional. A conic gradient holds its last colour all the way
       round to 360°, so without it the "wedge" is 30° of fade followed by 330° of solid
       tint over the entire page — which looks like a background, not a beam. */
    background-image: conic-gradient(from 0deg at 50% 50%,
        transparent 0deg,
        var(--sonar-beam) 30deg,
        transparent 30.5deg);
    animation: sonar-sweep var(--sonar-period) linear infinite;
}

@keyframes sonar-sweep {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* The returns. Each is offset from the origin in vmin on BOTH axes — never a mix of
   percentages — so its bearing from the centre is a fixed number rather than something
   that changes with the window's shape. That is what makes the timing below hold.

   Each delay is that bearing expressed as a fraction of one revolution, minus the 30°
   width of the wedge, because the leading edge arrives that much earlier than the
   angle the element sits at. The result is that a return lights *as the line reaches
   it* rather than on a timer that happens to run nearby. */
.sonar i:nth-child(n+2) {
    /* Size is per return, so the margins that centre it have to be derived rather than
       fixed. Close contacts read larger and distant ones smaller — signal strength
       falling off with range is most of what makes a display look like a display. */
    --blip: 8px;
    width: var(--blip);
    height: var(--blip);
    margin-left: calc(var(--blip) / -2);
    margin-top: calc(var(--blip) / -2);
    border-radius: 50%;
    background-image: radial-gradient(circle, var(--sonar-blip) 22%, transparent 68%);
    animation-name: sonar-blip;
    animation-duration: var(--sonar-period);
    animation-timing-function: ease-out;
    animation-iteration-count: infinite;
}

/* Each delay is exactly (bearing − 30) ÷ 360 × 9s, to the millisecond. Rounded to two
   decimals instead, half of them land a few milliseconds after the line has already
   passed — invisible on screen, but it makes the relationship look approximate when it
   is not, and the next person to move a return would have no rule to follow. */
/* bearing 34.7° */
.sonar i:nth-child(2) { left: calc(var(--sonar-x) + 18vmin); top: calc(var(--sonar-y) - 26vmin); animation-delay: 0.1174s; }
/* bearing 109.4° */
.sonar i:nth-child(3) { left: calc(var(--sonar-x) + 34vmin); top: calc(var(--sonar-y) + 12vmin); animation-delay: 1.986s; }
/* bearing 163.1° */
.sonar i:nth-child(4) { left: calc(var(--sonar-x) + 10vmin); top: calc(var(--sonar-y) + 33vmin); animation-delay: 3.3285s; }
/* bearing 222.5° */
.sonar i:nth-child(5) { left: calc(var(--sonar-x) - 22vmin); top: calc(var(--sonar-y) + 24vmin); animation-delay: 4.8128s; }
/* bearing 295.0° */
.sonar i:nth-child(6) { left: calc(var(--sonar-x) - 30vmin); top: calc(var(--sonar-y) - 14vmin); animation-delay: 6.6254s; }
/* bearing 346.8° */
.sonar i:nth-child(7) { left: calc(var(--sonar-x) -  8vmin); top: calc(var(--sonar-y) - 34vmin); animation-delay: 7.919s; }

/* Those six all sit around 31–36vmin out, which draws them as a ring of evenly spaced
   dots — a pattern, not a scan. These four break the range: one contact close in, three
   well out. The offsets stay in vmin on both axes for the same reason as before, so
   every bearing below is still a fixed number the delay can be derived from. */
/* bearing  73°, close in */
.sonar i:nth-child(8)  { --blip: 12px;  left: calc(var(--sonar-x) + 10vmin); top: calc(var(--sonar-y) -  3vmin); animation-delay: 1.083s; }
/* bearing 140°, far out */
.sonar i:nth-child(9)  { --blip: 6px;   left: calc(var(--sonar-x) + 35vmin); top: calc(var(--sonar-y) + 41vmin); animation-delay: 2.738s; }
/* bearing 255°, far out */
.sonar i:nth-child(10) { --blip: 5.5px; left: calc(var(--sonar-x) - 56vmin); top: calc(var(--sonar-y) + 15vmin); animation-delay: 5.625s; }
/* bearing 317°, far out */
.sonar i:nth-child(11) { --blip: 6px;   left: calc(var(--sonar-x) - 34vmin); top: calc(var(--sonar-y) - 37vmin); animation-delay: 7.185s; }

/* Bright at 0%, because 0% is the instant the line arrives — the delays above are what
   place that instant. Then a long decay, so the screen is never empty between passes. */
@keyframes sonar-blip {
    0%   { opacity: 1;  transform: scale(1.8); }
    5%   { opacity: .8; transform: scale(1); }
    34%  { opacity: 0;  transform: scale(1); }
    100% { opacity: 0;  transform: scale(1); }
}

/* The origin itself, ticking over once per revolution. */
.sonar::before {
    content: "";
    position: absolute;
    left: var(--sonar-x);
    top: var(--sonar-y);
    width: 5px;
    height: 5px;
    margin-left: -2.5px;
    margin-top: -2.5px;
    border-radius: 50%;
    background-color: var(--sonar-blip);
    animation: sonar-origin var(--sonar-period) linear infinite;
}

@keyframes sonar-origin {
    0%   { opacity: .85; }
    12%  { opacity: .25; }
    100% { opacity: .25; }
}

/* 7. Drafting — the only constructive effect. Everything else here is atmosphere that
   happens to the page; this one is somebody working something out on it. A faint grid,
   and every so often a dimension line, an arc or a height draws itself, holds long
   enough to be read, and is rubbed out.

   Marks in two groups. Three measurements drawing every 9, 12 and 15 seconds — periods
   that share no factor, so they never appear as a set and something is nearly always
   being drawn. Then an app icon's construction grid, whose parts are staggered so they
   read as one drawing being built rather than four unrelated marks.

   Every mark tours. Its animation runs four times its drawing period and contains four
   draws, each somewhere else on the page, so nothing ever reappears where it last was —
   36, 48, 60 and 52 seconds round for the line, arc, height and icon. CSS cannot pick a
   position per iteration, so the itinerary is written out; the move itself happens in
   the gap between one fade-out and the next draw, where the mark is at opacity 0 and
   travelling costs nothing to look at.

   The icon is a *template*, not a traced logo. Squircle, inscribed circle and centre
   cross is what an icon grid actually looks like before anything is drawn on it, and it
   claims to be none of the real icons in particular. */
.effect-drafting {
    --draft-grid: rgba(40, 72, 122, .09);
    --draft-major: rgba(40, 72, 122, .16);
    --draft-ink: rgba(28, 78, 148, .5);
    /* The construction lines sit under the outline they serve, as they do on the real
       template — the squircle is the drawing, the grid is the scaffolding. */
    --draft-grid-line: rgba(28, 78, 148, .26);
}

@media (prefers-color-scheme: dark) {
    .effect-drafting {
        --draft-grid: rgba(150, 200, 255, .06);
        --draft-major: rgba(150, 200, 255, .11);
        --draft-ink: rgba(152, 214, 255, .55);
        --draft-grid-line: rgba(152, 214, 255, .28);
    }
}

/* Minor grid every 44px, major every 220px — the fifth line heavier, as on paper. */
.effect-drafting::before {
    background-image:
        linear-gradient(0deg,  var(--draft-major) 1px, transparent 1px),
        linear-gradient(90deg, var(--draft-major) 1px, transparent 1px),
        linear-gradient(0deg,  var(--draft-grid)  1px, transparent 1px),
        linear-gradient(90deg, var(--draft-grid)  1px, transparent 1px);
    background-size: 220px 220px, 220px 220px, 44px 44px, 44px 44px;
}

/* The variant moves every mark, and moves each one differently — the offsets below are
   combined with different signs, scales and axes per mark further down, so the whole
   arrangement changes rather than sliding across as a block. The icon gets its own
   position outright, because a drawing that always appears in the same corner stops
   being something you notice. */
.draft-a { --dx:   0%; --dy:   0%; --ix: 66%; --iy: 45%; }
.draft-b { --dx:  11%; --dy:  -9%; --ix: 29%; --iy: 63%; }
.draft-c { --dx:  -8%; --dy:  10%; --ix: 74%; --iy: 69%; }
.draft-d { --dx:   5%; --dy:  16%; --ix: 36%; --iy: 32%; }

.draft i {
    background-color: var(--draft-ink);
}

/* A horizontal dimension, with a tick at each end. */
.draft i:nth-child(1) {
    left: calc(18% + var(--dx));
    top: calc(31% + var(--dy));
    width: 33%;
    height: 1px;
    transform-origin: 0 50%;
    animation: draft-line 36s ease-in-out infinite;
}

/* Each mark takes the variant offsets differently — one straight, one reversed, one with
   the axes swapped. Applying the same shift to all three would move the arrangement
   without changing it. */

.draft i:nth-child(1)::before,
.draft i:nth-child(1)::after {
    content: "";
    position: absolute;
    top: -4px;
    width: 1px;
    height: 9px;
    background-color: var(--draft-ink);
}

.draft i:nth-child(1)::before { left: 0; }
.draft i:nth-child(1)::after  { right: 0; }

@keyframes draft-line {
    0%    { opacity: 0; transform: translate(0, 0) scaleX(0); }
    0.75%    { opacity: 1; }
    2.75%    { opacity: 1; transform: translate(0, 0) scaleX(1); }
    6.75%    { opacity: 1; transform: translate(0, 0) scaleX(1); }
    8.25%    { opacity: 0; transform: translate(0, 0) scaleX(1); }
    21%     { opacity: 0; transform: translate(0, 0) scaleX(0); }
    24%     { opacity: 0; transform: translate(30vw, 9vh) scaleX(0); }
    25%    { opacity: 0; transform: translate(30vw, 9vh) scaleX(0); }
    25.75%    { opacity: 1; }
    27.75%    { opacity: 1; transform: translate(30vw, 9vh) scaleX(1); }
    31.75%    { opacity: 1; transform: translate(30vw, 9vh) scaleX(1); }
    33.25%    { opacity: 0; transform: translate(30vw, 9vh) scaleX(1); }
    46%     { opacity: 0; transform: translate(30vw, 9vh) scaleX(0); }
    49%     { opacity: 0; transform: translate(-6vw, 26vh) scaleX(0); }
    50%    { opacity: 0; transform: translate(-6vw, 26vh) scaleX(0); }
    50.75%    { opacity: 1; }
    52.75%    { opacity: 1; transform: translate(-6vw, 26vh) scaleX(1); }
    56.75%    { opacity: 1; transform: translate(-6vw, 26vh) scaleX(1); }
    58.25%    { opacity: 0; transform: translate(-6vw, 26vh) scaleX(1); }
    71%     { opacity: 0; transform: translate(-6vw, 26vh) scaleX(0); }
    74%     { opacity: 0; transform: translate(22vw, -14vh) scaleX(0); }
    75%    { opacity: 0; transform: translate(22vw, -14vh) scaleX(0); }
    75.75%    { opacity: 1; }
    77.75%    { opacity: 1; transform: translate(22vw, -14vh) scaleX(1); }
    81.75%    { opacity: 1; transform: translate(22vw, -14vh) scaleX(1); }
    83.25%    { opacity: 0; transform: translate(22vw, -14vh) scaleX(1); }
    96%     { opacity: 0; transform: translate(22vw, -14vh) scaleX(0); }
    99%     { opacity: 0; transform: translate(0, 0) scaleX(0); }
    100%   { opacity: 0; transform: translate(0, 0) scaleX(0); }
}

/* An arc: a circle with only two borders coloured, swung into place. */
.draft i:nth-child(2) {
    left: calc(22% - var(--dy));
    top: calc(64% - var(--dx));
    width: 120px;
    height: 120px;
    background-color: transparent;
    border: 1px solid transparent;
    border-top-color: var(--draft-ink);
    border-right-color: var(--draft-ink);
    border-radius: 50%;
    animation: draft-arc 48s ease-in-out infinite;
    animation-delay: -4s;
}

@keyframes draft-arc {
    0%    { opacity: 0; transform: translate(0, 0) rotate(-42deg) scale(.86); }
    1%    { opacity: 1; }
    3.25% { opacity: 1; transform: translate(0, 0) rotate(18deg) scale(1); }
    7.25% { opacity: 1; transform: translate(0, 0) rotate(18deg) scale(1); }
    8.75% { opacity: 0; transform: translate(0, 0) rotate(36deg) scale(1.05); }
    21%   { opacity: 0; transform: translate(0, 0) rotate(-42deg) scale(.86); }
    24%   { opacity: 0; transform: translate(46vw, -18vh) rotate(-42deg) scale(.86); }
    25%    { opacity: 0; transform: translate(46vw, -18vh) rotate(-42deg) scale(.86); }
    26%    { opacity: 1; }
    28.25% { opacity: 1; transform: translate(46vw, -18vh) rotate(18deg) scale(1); }
    32.25% { opacity: 1; transform: translate(46vw, -18vh) rotate(18deg) scale(1); }
    33.75% { opacity: 0; transform: translate(46vw, -18vh) rotate(36deg) scale(1.05); }
    46%   { opacity: 0; transform: translate(46vw, -18vh) rotate(-42deg) scale(.86); }
    49%   { opacity: 0; transform: translate(18vw, -40vh) rotate(-42deg) scale(.86); }
    50%    { opacity: 0; transform: translate(18vw, -40vh) rotate(-42deg) scale(.86); }
    51%    { opacity: 1; }
    53.25% { opacity: 1; transform: translate(18vw, -40vh) rotate(18deg) scale(1); }
    57.25% { opacity: 1; transform: translate(18vw, -40vh) rotate(18deg) scale(1); }
    58.75% { opacity: 0; transform: translate(18vw, -40vh) rotate(36deg) scale(1.05); }
    71%   { opacity: 0; transform: translate(18vw, -40vh) rotate(-42deg) scale(.86); }
    74%   { opacity: 0; transform: translate(58vw, 12vh) rotate(-42deg) scale(.86); }
    75%    { opacity: 0; transform: translate(58vw, 12vh) rotate(-42deg) scale(.86); }
    76%    { opacity: 1; }
    78.25% { opacity: 1; transform: translate(58vw, 12vh) rotate(18deg) scale(1); }
    82.25% { opacity: 1; transform: translate(58vw, 12vh) rotate(18deg) scale(1); }
    83.75% { opacity: 0; transform: translate(58vw, 12vh) rotate(36deg) scale(1.05); }
    96%   { opacity: 0; transform: translate(58vw, 12vh) rotate(-42deg) scale(.86); }
    99%   { opacity: 0; transform: translate(0, 0) rotate(-42deg) scale(.86); }
    100%  { opacity: 0; transform: translate(0, 0) rotate(-42deg) scale(.86); }
}

/* A height, drawn downward. */
.draft i:nth-child(3) {
    left: calc(79% + var(--dy));
    top: calc(19% - var(--dx));
    width: 1px;
    height: 29%;
    transform-origin: 50% 0;
    animation: draft-height 60s ease-in-out infinite;
    animation-delay: -6s;
}

@keyframes draft-height {
    0%    { opacity: 0; transform: translate(0, 0) scaleY(0); }
    0.75%    { opacity: 1; }
    3%    { opacity: 1; transform: translate(0, 0) scaleY(1); }
    7%    { opacity: 1; transform: translate(0, 0) scaleY(1); }
    8.5%  { opacity: 0; transform: translate(0, 0) scaleY(1); }
    21%   { opacity: 0; transform: translate(0, 0) scaleY(0); }
    24%   { opacity: 0; transform: translate(-52vw, 22vh) scaleY(0); }
    25%    { opacity: 0; transform: translate(-52vw, 22vh) scaleY(0); }
    25.75%    { opacity: 1; }
    28%    { opacity: 1; transform: translate(-52vw, 22vh) scaleY(1); }
    32%    { opacity: 1; transform: translate(-52vw, 22vh) scaleY(1); }
    33.5%  { opacity: 0; transform: translate(-52vw, 22vh) scaleY(1); }
    46%   { opacity: 0; transform: translate(-52vw, 22vh) scaleY(0); }
    49%   { opacity: 0; transform: translate(-26vw, 8vh) scaleY(0); }
    50%    { opacity: 0; transform: translate(-26vw, 8vh) scaleY(0); }
    50.75%    { opacity: 1; }
    53%    { opacity: 1; transform: translate(-26vw, 8vh) scaleY(1); }
    57%    { opacity: 1; transform: translate(-26vw, 8vh) scaleY(1); }
    58.5%  { opacity: 0; transform: translate(-26vw, 8vh) scaleY(1); }
    71%   { opacity: 0; transform: translate(-26vw, 8vh) scaleY(0); }
    74%   { opacity: 0; transform: translate(-66vw, 34vh) scaleY(0); }
    75%    { opacity: 0; transform: translate(-66vw, 34vh) scaleY(0); }
    75.75%    { opacity: 1; }
    78%    { opacity: 1; transform: translate(-66vw, 34vh) scaleY(1); }
    82%    { opacity: 1; transform: translate(-66vw, 34vh) scaleY(1); }
    83.5%  { opacity: 0; transform: translate(-66vw, 34vh) scaleY(1); }
    96%   { opacity: 0; transform: translate(-66vw, 34vh) scaleY(0); }
    99%   { opacity: 0; transform: translate(0, 0) scaleY(0); }
    100%  { opacity: 0; transform: translate(0, 0) scaleY(0); }
}

/* ── The icon construction grid ───────────────────────────────────────────────
   Four marks on one period, staggered so they build in the order a template is laid
   out: the squircle, then its concentric circles, then the orthogonal grid, then the
   diagonals. All four share one box — they are the same square — so position and size
   are set once here.

   The layout follows Apple's own icon grid rather than a rounded box with a cross
   through it. Two things carry the resemblance: the grid lines land on the tangents
   that matter (13% and 87% where the corner radius meets the straight edge, 35% and
   65% where the largest circle touches, 50% for centre), and the diagonals run corner
   to corner, so the whole thing gathers into the asterisks at the corner-radius
   centres that make the template recognisable. */
.draft i:nth-child(n+4) {
    left: var(--ix);
    top: var(--iy);
    width: 190px;
    height: 190px;
    margin-left: -95px;
    margin-top: -95px;
    background-color: transparent;
    animation-duration: 52s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-name: draft-icon;
}

/* The squircle. 22.37% is the corner radius iOS actually uses. */
.draft i:nth-child(4) {
    border: 1px solid var(--draft-ink);
    border-radius: 22.37%;
}

/* Three concentric circles, as rings cut out of one background rather than three more
   elements. `closest-side` makes the radius half the box, so the stops below are
   fractions of that radius: 78%, 55% and 34%. */
.draft i:nth-child(5) {
    animation-delay: .45s;
    background-image:
        radial-gradient(circle closest-side at 50% 50%, transparent 0 77.4%, var(--draft-ink) 77.4% 78%, transparent 78%),
        radial-gradient(circle closest-side at 50% 50%, transparent 0 54.4%, var(--draft-ink) 54.4% 55%, transparent 55%),
        radial-gradient(circle closest-side at 50% 50%, transparent 0 33.4%, var(--draft-ink) 33.4% 34%, transparent 34%);
}

/* The orthogonal grid: five lines each way, on the tangents rather than evenly spaced. */
.draft i:nth-child(6) {
    animation-delay: .9s;
    background-image:
        linear-gradient(90deg, transparent 0 calc(13% - .5px), var(--draft-grid-line) calc(13% - .5px) calc(13% + .5px), transparent calc(13% + .5px)),
        linear-gradient(90deg, transparent 0 calc(35% - .5px), var(--draft-grid-line) calc(35% - .5px) calc(35% + .5px), transparent calc(35% + .5px)),
        linear-gradient(90deg, transparent 0 calc(50% - .5px), var(--draft-grid-line) calc(50% - .5px) calc(50% + .5px), transparent calc(50% + .5px)),
        linear-gradient(90deg, transparent 0 calc(65% - .5px), var(--draft-grid-line) calc(65% - .5px) calc(65% + .5px), transparent calc(65% + .5px)),
        linear-gradient(90deg, transparent 0 calc(87% - .5px), var(--draft-grid-line) calc(87% - .5px) calc(87% + .5px), transparent calc(87% + .5px)),
        linear-gradient(180deg, transparent 0 calc(13% - .5px), var(--draft-grid-line) calc(13% - .5px) calc(13% + .5px), transparent calc(13% + .5px)),
        linear-gradient(180deg, transparent 0 calc(35% - .5px), var(--draft-grid-line) calc(35% - .5px) calc(35% + .5px), transparent calc(35% + .5px)),
        linear-gradient(180deg, transparent 0 calc(50% - .5px), var(--draft-grid-line) calc(50% - .5px) calc(50% + .5px), transparent calc(50% + .5px)),
        linear-gradient(180deg, transparent 0 calc(65% - .5px), var(--draft-grid-line) calc(65% - .5px) calc(65% + .5px), transparent calc(65% + .5px)),
        linear-gradient(180deg, transparent 0 calc(87% - .5px), var(--draft-grid-line) calc(87% - .5px) calc(87% + .5px), transparent calc(87% + .5px));
}

/* The diagonals, corner to corner. They overhang the box on every side — as they do on
   the real template, where the construction lines run past the artwork. */
.draft i:nth-child(7) {
    animation-delay: 1.35s;
    width: 240px;
    height: 240px;
    margin-left: -120px;
    margin-top: -120px;
    background-image:
        linear-gradient(45deg,  transparent calc(50% - .5px), var(--draft-grid-line) calc(50% - .5px) calc(50% + .5px), transparent calc(50% + .5px)),
        linear-gradient(-45deg, transparent calc(50% - .5px), var(--draft-grid-line) calc(50% - .5px) calc(50% + .5px), transparent calc(50% + .5px));
}

/* The icon does not draw in the same place twice. The `--ix`/`--iy` variant only settles
   where it starts, so within a single visit it would otherwise reappear on the same spot
   every cycle, which is the moment a thing stops being worth glancing at.

   CSS cannot pick a new position per iteration, so the cycle is four times as long and
   contains four draws, each somewhere else. The move happens in the gap: between one
   fade-out and the next draw the element is at opacity 0, and translating it there costs
   nothing to look at. Thirteen seconds a draw, fifty-two round the tour.

   The stagger on the four parts survives this. Each runs the same keyframes offset by its
   own delay, so at any moment a later part may still be at the previous position — but it
   is invisible until its own draw begins, by which time it has arrived. The teleport
   window is four per cent of the cycle, a little over two seconds, comfortably longer
   than the 1.35s spread between first part and last. Shorten one without the other and
   the grid will tear itself in half across the page. */
@keyframes draft-icon {
    /* first */
    0%    { opacity: 0; transform: translate(0, 0) scale(.84); }
    1%    { opacity: 1; }
    2.8%  { opacity: 1; transform: translate(0, 0) scale(1); }
    7.7%  { opacity: 1; transform: translate(0, 0) scale(1); }
    9.4%  { opacity: 0; transform: translate(0, 0) scale(1.03); }
    20%   { opacity: 0; transform: translate(0, 0) scale(.84); }
    24%   { opacity: 0; transform: translate(-24vw, 13vh) scale(.84); }

    /* second */
    25%   { opacity: 0; transform: translate(-24vw, 13vh) scale(.84); }
    26%   { opacity: 1; }
    27.8% { opacity: 1; transform: translate(-24vw, 13vh) scale(1); }
    32.7% { opacity: 1; transform: translate(-24vw, 13vh) scale(1); }
    34.4% { opacity: 0; transform: translate(-24vw, 13vh) scale(1.03); }
    45%   { opacity: 0; transform: translate(-24vw, 13vh) scale(.84); }
    49%   { opacity: 0; transform: translate(16vw, -17vh) scale(.84); }

    /* third */
    50%   { opacity: 0; transform: translate(16vw, -17vh) scale(.84); }
    51%   { opacity: 1; }
    52.8% { opacity: 1; transform: translate(16vw, -17vh) scale(1); }
    57.7% { opacity: 1; transform: translate(16vw, -17vh) scale(1); }
    59.4% { opacity: 0; transform: translate(16vw, -17vh) scale(1.03); }
    70%   { opacity: 0; transform: translate(16vw, -17vh) scale(.84); }
    74%   { opacity: 0; transform: translate(-8vw, 22vh) scale(.84); }

    /* fourth */
    75%   { opacity: 0; transform: translate(-8vw, 22vh) scale(.84); }
    76%   { opacity: 1; }
    77.8% { opacity: 1; transform: translate(-8vw, 22vh) scale(1); }
    82.7% { opacity: 1; transform: translate(-8vw, 22vh) scale(1); }
    84.4% { opacity: 0; transform: translate(-8vw, 22vh) scale(1.03); }
    95%   { opacity: 0; transform: translate(-8vw, 22vh) scale(.84); }
    99%,
    100%  { opacity: 0; transform: translate(0, 0) scale(.84); }
}

/* Every effect stops dead for a reader who asked for less motion. The static gradients
   stay — a still starfield or a still wash is not motion — but the aberration has no
   text-shadow outside its keyframes, so it simply disappears. */
@media (prefers-reduced-motion: reduce) {
    body.landing::before,
    body.landing::after,
    /* Qualified with `body.landing` on purpose, and it is not decoration. Every animation
       on these elements is declared on an `:nth-child()` selector — one class-level above
       a bare `.sky i` — so the unqualified list this replaced on 2026-08-06 lost the
       cascade to all eleven of them and stopped precisely none. `display: none` below was
       doing the entire job on its own. Both halves now hold independently, which is what
       the comment above always claimed. Do not un-qualify them for symmetry with `.ball`,
       whose animations really are declared on `.ball i` and need no help. */
    body.landing .sky i,
    body.landing .film i,
    body.landing .sonar i,
    /* The sonar's origin dot animates on a pseudo-element of the container, not on an
       `<i>`, so it is not reachable through any of the lines above. */
    body.landing .sonar::before,
    body.landing .draft i,
    .ball i,
    .ball i i,
    .ball i i i,
    .effect-aberration .claim,
    /* The hover rule outranks the plain one, so it has to be named here too — without
       it, pointing at the headline would restart exactly what was switched off. */
    .effect-aberration .claim:hover {
        animation: none;
    }

    /* These have no resting state worth showing — parked at opacity 0 they are just
       invisible specks and an invisible line — so they are removed outright. */
    .sky,
    .film,
    .sonar,
    .draft,
    .ball {
        display: none;
    }
}

@media only screen and (min-width:771px) and (max-width:1024px) {
    h1 {
    font-size: 25px;
    line-height: 38px
    }

    h2 {
        font-size: 22px;
        line-height: 35px
    }

    .claim {
        font-size: 60px;
        line-height: 72px;
    }

    button,
    .pill-button,
    h3 {
        font-size: 20px
    }

    h4 {
        font-size: 18px;
        line-height: 22px;
    }
}

@media only screen and (min-width:1024px) {
    h1 {
        font-size: 28px;
        line-height: 32px
    }

    h2 {
        font-size: 25px;
        line-height: 35px
    }

    .claim {
        font-size: 82px;
        line-height: 94px;
    }

    button,
    .pill-button,
    h3 {
        font-size: 20px
    }

    h4 {
        font-size: 18px;
        line-height: 22px;
    }
}

.bold,
.bold-italic,
.italic,
.regular {
    font-style: normal;
    font-weight: 400
}

p {
    font-size: 16px
}

.inset {
    margin-left: 14px
}

.footer-box p {
    margin-left: 10px
}

.app-site-title {
    font-size: 38px;
    line-height: 52px
}

.regular {
    font-family: 'Inclusive Sans Regular'
}

.italic {
    font-family: 'Inclusive Sans Regular italic'
}

.bold {
    font-family: 'Inclusive Sans Bold'
}

.bold-italic {
    font-family: 'Inclusive Sans Bold Italic'
}

.bullet-point {
    font-size: 145px
}

.right {
    text-align: right
}

.center {
    text-align: center
}

.left {
    text-align: left
}

.app-site-title,
.inset,
h1 {
    margin-top: 15px;
    margin-bottom: 15px;
}

.screenshots p,
.tag {
    text-align: center;
    display: block
}

.dimensions {
    display: block;
    width: auto;
    padding: 0 16px;
    /*min-height:;*/
}

@media only screen and (min-width:771px) and (max-width:1024px) {
    .dimensions {
        padding: 0 24px
    }
}

@media only screen and (min-width:1024px) {
    .dimensions {
        width: 900px;
        margin: 0 auto;
        padding: 0 24px
    }
    .element {
        width: 900px
    }
}

.footer-dimensions {
    display: block;
    width: auto;
    padding: 0 36px;
    /*min-height:;*/
}

@media only screen and (min-width:771px) and (max-width:1024px) {
    .footer-dimensions {
        padding: 0 56px
    }
}

@media only screen and (min-width:1024px) {
    .footer-dimensions {
        width: 900px;
        margin: 0 auto;
        padding: 0 56px
    }
}

.content {
    display: block;
    position: relative;
    margin-top: 18px;
    padding-bottom: 5px;
}

.content-text {
    display: block;
    position: relative;
    margin-top: 65px;
    padding-bottom: 5px;
}

.slider-inset {
    margin-top: 330px;
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .slider-inset {
        margin-top: 400px
    }
}

@media only screen and (min-width:1401px) {
    .slider-inset {
        margin-top: 500px
    }
}

.element {
    margin: 0 auto;
    padding-top: 16px;
    padding-bottom: 16px
}

.content-box {
    border: 1px #333 solid;
    border-radius: 8px;
    margin: 16px 24px;
    color: #333;
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .content-box {
        margin: 16px auto;
    }
}

@media only screen and (min-width:1401px) {
    .content-box {
        margin: 16px auto;
    }
}

@media (prefers-color-scheme: dark) {
    .content-box {
        border: 1px #eee solid;
        color: #eee;
    }
}

.tag {
    display: block;
    color: #333;
    float:left;
    height: 30px;
    font-size: 16px;
    margin: 12px 12px auto 0px;
    padding: 4px 12px 24px 12px;
    border-radius: 15px
}

@media (prefers-color-scheme: dark) {
    .tag {
        color: #eee;
    }
}

/* Restated, not inherited. `a:hover` sits in the reset selector list (line 84) that
   zeroes margin and padding, and `a:hover` (0,1,1) beats `.tag` (0,1,0) — so a `.tag`
   placed directly on an `<a>` loses its 12px top margin the instant the pointer
   touches it, and the button jumps up the page. Same failure and same fix as
   `.footer-social` (line ~2082).

   Every button on the site avoids this structurally, by putting `.tag` on a `<div>`
   or `<button>` *inside* the anchor, where `a:hover` cannot reach it — see
   `lib/inc/header-buttons.php`. That is still the shape to copy. This rule exists so
   that the shape is a preference rather than a trap: the next `.tag` written straight
   onto a link will look wrong, not move. Padding is restated for the same reason,
   even though it is inert wherever `.pill-button` is also present, since that class
   marks its own padding `!important`. */
.tag:hover,
.tag:active,
.tag:focus {
    margin: 12px 12px auto 0px;
    padding: 4px 12px 24px 12px;
}

.tag-center {
    display: block;
    color: #333;
    height: 30px;
    font-size: 16px;
    margin: 12px auto;
    padding: 5px 12px 22px;
    border-radius: 15px
}

/* The mirror of the `.tag:hover` guard above, for the centred variant. */
.tag-center:hover,
.tag-center:active,
.tag-center:focus {
    margin: 12px auto;
    padding: 5px 12px 22px;
}

.tag-link,
.tag-link:hover {
    color: #333;
}

.auricula .tag, .auricula .tag-center {
    border: 2px solid #08b704;
    color: #08b704;
}

@media (prefers-color-scheme: dark) {
    .auricula .tag, .auricula .tag-center {
        border: 2px solid #C22FD3;
        color: #C22FD3;
    }
}

.nextsize .tag, .nextsize .tag-center {
    border: 2px solid #BE3E30;
    color: #BE3E30;
}

@media (prefers-color-scheme: dark) {
    .nextsize .tag, .nextsize .tag-center {
        border: 2px solid #BE3E30;
        color: #BE3E30;
    }
}

@media (prefers-color-scheme: dark) {
    .tag-center {
        color: #eee;
    }
    .tag-link,
    .tag-link:hover {
        color: #eee;
    }
}

header {
    position: fixed;
    height: 51px;
    z-index: 10
}

.site-title {
    padding-top: 14px;
    padding-bottom: 23px;
    float: left
}

@media only screen and (min-width:771px) {
    .site-title {
        padding-top: 12px;
        padding-bottom: 25px;
    }
}


.app-list {
    padding-bottom: 15px;
    padding-top: 40px;
}

.app-list li {
    float: none;
}

@media only screen and (min-width:771px) {
    .app-list {
        margin: 0 auto;
    }
    .app-list li {
        float: left;

    }
}

@media only screen and (min-width:771px) and (max-width:1100px) {
    .app-list {
        width: 680px;
    }
}

@media only screen and (min-width:1102px) {
    .app-list {
        width: 1020px;
    }
}

.above-slider {
    position: relative;
    z-index: 2
}

.slider-image {
    position: relative;
    display: block;
    margin: 0 auto;
    padding-top: 80px
}

@media only screen and (min-width:771px) {
    .slider-image {
        padding-top: 100px
    }
}

.slider img {
    height: 170px;
    width: 145px
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .slider img {
        height: 250px;
        width: 213px
    }
}

@media only screen and (min-width:1401px) {
    .slider img {
        height: 350px;
        width: 300px
    }
}

.app-icon-container {
    margin: 20px auto 40px;
    width: 340px;
}

/* The footer carries no background of its own. It used to repeat the page colour, which
   was invisible against an identically coloured page and became a visible slab the
   moment anything was drawn behind it — see the landing-page effects (R-030). */
.menu-button {
    background-color: #fefefe
}

@media (prefers-color-scheme: dark) {
    .menu-button {
        background-color: #222;
    }
}

.footer a p, .footer a:active p  {
    color:#757575;
}

@media (prefers-color-scheme: dark) {
    .footer a p, .footer a:active p {
        color:#aaa;
    }
}


.download-badge {
    position: relative;
    display: block;
    margin: 0 auto;
    height: 40px;
    width: 135px;
    padding-bottom: 25px
}

.download-badge-container {
    width: 145px;
    margin: 0 auto
}

input[type=checkbox] {
    background-color: #fff;
    border: 1px solid #dfdfdf;
    border-radius: .15em;
    display: grid;
    font: inherit;
    height: 1.15em;
    margin: 0 8px 0 0;
    place-content: center;
    transform: translateY(0.25em);
    width: 1.15em;
}

label {
    display: flex;
    align-items: baseline;
}

.footer {
    position: relative;
    display: block;
    border-top: 1px;
    width: 100%;
    height: auto;
    z-index: 2;
    margin: 0 auto;
}

@media only screen and (min-width:771px) and (max-width:1024px) {
    .footer-content {
        padding-top: 25px
    }
    .footer-box {
        width: 25%
    }
}

@media only screen and (min-width:1024px) {
    .footer-content {
        padding-top: 25px
    }
    .footer-box {
        width: 25%
    }
}

.footer-headline {
    margin-bottom: 8px
}

/* Four-column footer. The .footer-box width:25% rules above only take effect from
   771px up; below that each box stays full width and the columns stack. */
/* No horizontal padding of its own. The footer already sits inside a `.dimensions`, whose
   gutter (16px, 24px from 771px) is the site's single left edge — every column heading and
   link here has to start on it, level with the navigation and the claim. The 12px this
   used to add was a second, private gutter that put the whole footer 12px to the right of
   everything above it on every page and at every width. */
.footer-content {
    display: flex;
    flex-wrap: wrap;
    text-align: left;
    max-width: 1024px;
    margin: 0 auto;
    padding: 25px 0;
}

/* Base is the stacked mobile case; the 25% columns are re-declared here rather than
   relying on the older media queries above, because those carry no extra specificity
   and would lose to any later .footer-box rule in this file. */
.footer-box {
    width: 100%;
    padding-bottom: 18px;
}

@media only screen and (min-width:771px) {
    .footer-box {
        width: 25%;
        padding-bottom: 0;
    }
}

.footer-box a p {
    margin: 2px 0;
}

/* The column headings are links. They must keep the heading colour rather than the
   grey of .footer a p, and need a visible hover/focus state — bold text alone gives
   no hint that it is clickable. */
.footer-headline-link,
.footer-headline-link:active {
    color: inherit;
    text-decoration: none;
}

.footer-headline-link:hover,
.footer-headline-link:focus-visible {
    text-decoration: underline;
}

/* Outgoing-link mark, set inline after the label. `em` rather than pixels so it tracks
   the footer text if that size ever changes, and a small lift off the baseline because
   the glyph's box is optically bottom-heavy against lowercase letters. */
.footer-external {
    display: inline-block;
    width: 0.8em;
    height: 0.8em;
    margin-left: 0.35em;
    vertical-align: -0.05em;
    fill: currentColor;
}

/* Social marks are inlined SVG rather than image files, so they inherit the link
   colour through currentColor and need no separate dark-mode asset. */
.footer-social {
    display: inline-block;
    margin: 8px 6px 0 0;
    color: #757575;
    text-decoration: none;
}

/* Two global rules have to be beaten here, both more specific than .footer-social:
   `a:hover` sits in the reset selector list (line 86) that zeroes margin and padding,
   which collapsed the gap between icons on hover; and `a:hover` at line 118 adds an
   underline. Margin is therefore restated, not just the decoration. No hover colour —
   the other footer links do not react to hover at the moment. */
.footer-social:hover,
.footer-social:active,
.footer-social:focus {
    margin: 8px 6px 0 0;
    color: #757575;
    text-decoration: none;
}

.footer-social svg {
    display: block;
    width: 18px;
    height: 18px;
    fill: currentColor;
}

@media (prefers-color-scheme: dark) {
    .footer-social,
    .footer-social:hover,
    .footer-social:active,
    .footer-social:focus {
        color: #aaa;
    }
}

/* ---- Shared page type: lede, fine print, two columns ------------------------
   Three type levels only: lede, body, fine print. Built for /newsletter/ and
   generalised when /crew/ needed the same three, so the two text pages of the site
   read as one thing.

   These carried `.newsletter-lede` / `.newsletter-note` / `.subscribe-columns` /
   `.subscribe-col` alongside the new names for one deploy cycle. That was not
   decoration: this site is uploaded by hand with no manifest and nothing checking what
   went up, so a bare rename needs site.css and newsletter/index.php in the *same*
   upload or the live signup form loses its layout. With both names bound the order did
   not matter. The aliases were removed on 2026-08-08 once the live page was confirmed
   to be emitting the new names. Reinstate them for a cycle if these are ever renamed
   again — the window, not the names, is the point. */

/* The measure is capped on the children, not on .newsletter-body / .crew-body: those
   elements also carry .dimensions, which above 1024px is `width: 900px; margin: 0
   auto`. Capping the container would shrink that box and re-centre it, pulling the
   text out of alignment with the h1 above. */
.page-lede {
    font-size: 20px;
    line-height: 30px;
    margin-bottom: 32px;
    max-width: 34em;
}

.subscribe-field {
    margin-bottom: 20px;
}

/* The global `label { display: flex }` (line 765) would put the hint on the same
   flex line as the label text and stop the label sitting above its input. */
.subscribe-form label {
    display: block;
    font-size: 15px;
    line-height: 22px;
    margin-bottom: 6px;
}

.subscribe-hint,
.subscribe-required {
    color: #757575;
}

/* The channel checkbox (R-021) is the one row where the label belongs beside the
   control, not above it. This restores the site-wide checkbox-inside-label row from
   line 765 that `.subscribe-form label` above deliberately switches off for the
   stacked text fields, and drops that rule's 6px gap, which here would push the text
   off the checkbox's baseline. */
.subscribe-form label.subscribe-choice {
    display: flex;
    align-items: baseline;
    margin-bottom: 6px;
}

.subscribe-group-label {
    display: block;
    font-size: 15px;
    line-height: 22px;
    margin-bottom: 8px;
}

/* Two columns with a rule between them, stacking below 771px. Two users, and the
   pattern says the same thing in both: these are peers, and neither is a step on the
   way to the other.
     /newsletter/ — the form: fields and the submit button on the left, every channel
       choice on the right. Stacked, the fields come first and the choices land
       directly above the button, which is still a sane order.
     /crew/ — the two ways into the crew, mail and Signal.
   Nothing is capped to one column here beyond the 880px; a column's own contents cap
   themselves where they need to.

   Left at 1fr 1fr rather than being made proportional: the rule reads as a division
   only while the two sides are equal, and an uneven split would quietly rank them. */
.split-columns {
    display: grid;
    grid-template-columns: 1fr;
    max-width: 880px;
}

/* Stacked, the second column just needs air above it — a vertical rule would be
   meaningless with nothing beside it. */
.split-col + .split-col {
    margin-top: 28px;
}

@media only screen and (min-width:771px) {
    .split-columns {
        grid-template-columns: 1fr 1fr;
    }
    /* The gutter is built from symmetric padding rather than column-gap, so the rule
       sits centred between the two columns instead of hard against the right one.
       Padding is set on every column and then flipped on the second, rather than
       selected with :first-child — the columns are not the only children of the
       grid's parent chain, and the positional selector did not hold. */
    .split-col {
        padding-right: 48px;
    }
    .split-col + .split-col {
        margin-top: 0;
        padding-right: 0;
        border-left: 1px solid #8c8c8c;
        padding-left: 48px;
    }
}

/* MOBILE ONLY: put the submit button after the topic choices.
   Added 2026-08-11 — stacked, the two-column form put Subscribe above the thing it
   subscribes you to, because the button lives inside the FIRST column and a stacked
   grid emits column one entire before column two.

   `display: contents` is what makes this fixable at all. The button is a GRANDCHILD
   of the grid, and `order` only acts on direct grid items — so the wrappers are
   dissolved here, their children become the grid's own items, and those can be
   ordered individually. The wrappers are plain divs with no semantics and, below
   771px, no styling of their own to lose (their padding and the rule between them
   live in the min-width branch), so nothing goes missing with the boxes.

   THE NOTE TRAVELS WITH THE BUTTON, and that is the load-bearing part. R-015 makes
   submitting the form the act of consent, and R-027 requires the purpose limitation,
   the withdrawal route and the Art. 13 link to stand beside it. Moving the button
   alone would leave that text most of a phone screen above the button it explains.
   Same `order` value on both keeps them adjacent and in source order.

   Ordered by class, never by position: `:nth-child`/`:first-child` have already
   failed once on this grid, and a positional rule here would silently re-point at
   the wrong field the next time a row is added. `.subscribe-actions` and
   `.subscribe-topics` exist solely as hooks for this block.

   Resulting order below 771px: email, name, topics, Studio Crew, note, Subscribe.
   Above 771px nothing in here applies and the two columns are unchanged — verified
   pixel-identical against the live page at 1280px on 2026-08-11.

   ⚠ KNOWN LIMIT, measured not assumed: `order` moves the BOX, never the focus. The
   DOM is untouched, so keyboard tab order still reaches Subscribe FIFTH while it is
   drawn tenth and last. That is a WCAG 2.4.3 (Focus Order) mismatch and it cannot be
   fixed from CSS — only by reordering the markup, which would move the button out of
   column one and change the desktop layout this rule exists to preserve. The site
   ships no JavaScript of its own (R-030), so there is no runtime option either.
   The harm is bounded rather than absent: the four topic boxes ship ticked (R-025),
   so submitting early still subscribes to every topic; only Studio Crew, which is
   deliberately unticked, would be missed. Tracked in Things — do not "tidy" this
   comment away without reading that item. */
@media only screen and (max-width: 770px) {
    .subscribe-form .split-col {
        display: contents;
    }

    /* Replaces the gap `.split-col + .split-col` used to provide; that rule still
       matches but cannot paint, because its element no longer generates a box. */
    .subscribe-form .subscribe-topics {
        margin-top: 28px;
    }

    .subscribe-form .page-note,
    .subscribe-form .subscribe-actions {
        order: 1;
    }

    .subscribe-form .page-note {
        margin-top: 28px;
    }

    /* The whole page scrolled sideways by 16px until 2026-08-11, on every viewport
       below this breakpoint, and these two fields were the entire cause.

       Nothing in this stylesheet sets `box-sizing`, so everything is content-box:
       `width: 100%` sizes the CONTENT alone and the 28px of padding and 4px of border
       are added on top. At 375px the container insets 16px a side, leaving 343 — and
       343 + 28 + 4 is 375, starting at x=16, so the right edge landed at 391. The
       overflow was exactly the padding and border, at every width below 771px, because
       the container takes 32 away and the field adds the same 32 back.

       Scoped here and NOT set globally. Above this breakpoint the fields sit inside a
       grid column with 48px of gutter padding, so the extra 32 still lands inside the
       column and there is nothing to fix; border-box there would narrow them by 32px
       for no reason. A global `* { box-sizing: border-box }` is the usual advice and
       the wrong call in this file — no build step, no tests, and dozens of components
       combining fixed pixel widths with padding, all of which would resize at once. */
    .subscribe-form input[type=email],
    .subscribe-form input[type=text] {
        box-sizing: border-box;
    }
}

.subscribe-choices {
    display: grid;
    grid-template-columns: 1fr;
    max-width: 420px;
}

/* The rule marks Studio Crew as a different kind of commitment from the topic
   checkboxes above it, not just another topic. */
.subscribe-crew {
    border-top: 1px solid #8c8c8c;
    padding-top: 16px;
    margin-top: 16px;
    max-width: 34em;
}

.subscribe-crew-more {
    font-size: 14px;
    line-height: 22px;
    margin-top: 6px;
    margin-left: 26px;
}

.subscribe-crew-more a {
    color: #757575;
    text-decoration: underline;
}

@media (prefers-color-scheme: dark) {
    .subscribe-crew-more a {
        color: #aaa;
    }
}

/* The global reset strips input styling, so the fields need their own box —
   without this they render as unbordered text runs. */
.subscribe-form input[type=email],
.subscribe-form input[type=text] {
    display: block;
    width: 100%;
    max-width: 420px;
    font-size: 17px;
    font-family: inherit;
    padding: 12px 14px;
    border: 2px solid #8c8c8c;
    border-radius: 4px;
    background-color: transparent;
    color: inherit;
}

.subscribe-form input[type=email]:focus,
.subscribe-form input[type=text]:focus {
    border-color: #333;
}

/* The site's one real button, filled — offered where an ACTION is, as against
   `.tag`/`.pill-button`, which have no background or border at all and are the
   treatment for a row of place names (the section navigation). Built for
   /newsletter/'s submit; generalised as `.action-button` on 2026-08-08, when /crew/
   turned out to be using the navigation treatment for its two calls to action and
   nobody could see they were buttons.

   Deliberately NOT .tag/.pill-button: `.tag` carries `float: left` (line ~200), which
   took the button out of flow and let the following paragraph wrap beside it.

   `.subscribe-submit` is an `<input>` and stands alone; `.action-button` is a `<div>`
   inside an `<a>`, which is the site's shape for a link that looks like a button (it
   keeps `a:hover` off the box — see the `.tag:hover` guard for what happens when the
   class sits on the anchor itself). Two things follow, and both are load-bearing:
   `display: inline-block` stops the box stretching the full column, AND it makes the
   box an atomic inline, which is the only thing that keeps the global
   `a:hover { text-decoration: underline }` (line 118) from striking a line across a
   filled button. Decoration propagates into ordinary block descendants and cannot be
   switched off from inside them; it does not enter an atomic inline at all.

   R-031: this is a filled label, so its BOX meets the gutter and its text sits its own
   padding further in. Do not pull it left by a negative margin — that variant shipped
   and was reversed on 2026-08-07. */
.action-button,
.subscribe-submit {
    cursor: pointer;
    font-family: inherit;
    font-size: 16px;
    padding: 12px 24px;
    border: 2px solid #333;
    border-radius: 4px;
    background-color: #333;
    color: #fefefe;
}

.subscribe-submit {
    display: block;
}

.action-button {
    display: inline-block;
    margin: 8px 0 16px;
}

.action-button:hover,
.subscribe-submit:hover {
    opacity: .85;
}

.page-note {
    font-size: 14px;
    line-height: 22px;
    color: #757575;
    margin-bottom: 14px;
    max-width: 34em;
}

.page-note a {
    color: inherit;
    text-decoration: underline;
}

@media (prefers-color-scheme: dark) {
    .subscribe-form input[type=email]:focus,
    .subscribe-form input[type=text]:focus {
        border-color: #eee;
    }
    .subscribe-hint,
    .subscribe-required,
    .page-note {
        color: #aaa;
    }
    .action-button,
    .subscribe-submit {
        border-color: #eee;
        background-color: #eee;
        color: #222;
    }
}

/* ---- Crew page --------------------------------------------------------------
   The page is a long argument in short sections, so the whole job here is entry
   points and measure. It uses the shared lede and fine print above, plus
   .split-columns for the two channels; what is left is the spacing between the
   sections and the cap on the body text. */

/* On this page the measure is the columns' job, not a cap's. Every paragraph that is
   not inside a `.split-col` runs the full width of the container — the lede, the
   denial paragraph under the first pair, and "Leaving" — so that their right edge
   lands on the right edge of the columns and the page reads as one block rather than
   as a narrow strip with three grids hanging off it. Decided in conversation on
   2026-08-07, paragraph by paragraph, after the capped version was built and looked
   like two different pages stacked.

   The cost is real and was taken knowingly: at 1280px a full-width line is roughly 95
   characters against the ~75 a 34em cap holds. What makes it affordable is that only
   four paragraphs are ever full width, none longer than four lines; everything with
   any length to it sits in a column, where the column *is* the measure. Do not
   "restore" the cap because the body text measures long — that is the trade, and
   re-capping it would put the lede and the columns back out of alignment.

   The overrides below are needed because `.page-lede` and `.page-note` carry their own
   34em from the shared block further up, which this page's `p` rule cannot outrank.
   /newsletter/ keeps every cap: its lede sits above a form, with no wide block beneath
   it to align to. */
.crew-body p {
    margin-bottom: 16px;
}

.crew-body .page-lede {
    max-width: none;
}

/* The reset (line 84) zeroes the margins of h1–h6, and only h1 gets them back (the
   `.app-site-title, .inset, h1` rule). So every heading level below h1 has to state
   its own spacing or the sections run together — which is what this page did when
   the section labels were bold run-ins inside the paragraphs.

   Asymmetric on purpose: the space above a heading belongs to the section break, the
   space below it only has to hold the heading to its own first line. */
.crew-body h2 {
    margin-top: 40px;
    margin-bottom: 12px;
}

.crew-body h3 {
    margin-bottom: 8px;
}

/* The rule between two columns is a grid item's border, so it spans that item's whole
   height — including any top margin on its first child. An h2 that keeps its 40px
   section break inside the column therefore starts the rule 40px above the heading it
   is meant to sit beside, which reads as a stray line hanging off the top. The break
   moves to the container, which pushes the component down as a whole and leaves the
   rule starting level with the headings. */
.crew-body .split-columns {
    margin-top: 12px;
}

/* The first pair follows the lede directly, with no h2 between them to carry the
   section break — and adjacent margins collapse to the larger of the two, so the 12px
   above plus the lede's 32px below yielded 32px, less than any other break on the
   page. Stated as 40px, it collapses to the same 40px an h2 section break gives.

   Deliberately only the pair that follows the lede. The channel columns keep the
   tighter 12px because the sentence above them ("Two parts, and you can take either
   one on its own") introduces them and should stay attached; the lede introduces the
   page, not the pair beneath it. */
.crew-body .page-lede + .split-columns {
    margin-top: 40px;
}

.crew-body .split-col > h2:first-child {
    margin-top: 0;
}

/* The paragraph that denies the exchange the get/give columns would otherwise imply
   (R-035). It is deliberately outside the grid and full width — inside a column it
   would read as a footnote to that column alone. The extra air above separates it
   from the columns without a rule, which here would read as a divider between two
   sections rather than as one sentence answering both. Nothing else on the page uses
   this class, and that is the point: it is a position, not a style.

   Note the top margin has to be stated rather than left to the columns' bottom: grid
   items establish their own block formatting contexts, so no margin collapses out of
   them and there is nothing below the shorter column to push against. */
.crew-body .crew-caveat {
    margin-top: 28px;
}

/* The QR code always sits on its own white plate, in both colour schemes. The site's
   dark background is the reason: a scanner needs the quiet zone and the light modules
   to actually be light, and an inverted or transparent-backed code is unreliable to
   read even when it looks fine to the eye. The padding is that quiet zone — do not
   trim it to tighten the layout, it is part of the code.

   Sized in px rather than a percentage so it stays scannable in a narrow column
   instead of shrinking with it. */
/* `display: block` with `width: fit-content`, not `inline-block`: the join button above
   it is an inline `<a>`, so an inline-block plate shares its line box and lands to the
   right of the button, baseline-aligned — which puts the code half way across the
   column and drops the button's label to the bottom of it. It still has to shrink-wrap,
   though, or the white plate stretches the full column width. */
.crew-qr {
    display: block;
    width: fit-content;
    margin: 4px 0 16px;
    padding: 10px;
    background-color: #fff;
    border-radius: 4px;
    line-height: 0;
}

.crew-qr img {
    display: block;
    width: 160px;
    height: 160px;
}

.dropdown {
    position: relative;
    display: inline-block;
    float: right;
    width: 32px;
}

.menu-button {
    height: 32px;
    float: right;
    text-align: right;
    margin: 15px 0 18px 100px;
    padding: 3px 8px 8px;
    border: 2px solid #08b704;
    border-radius: 3px
}

.auricula .menu-button  {
    border: 2px solid #08b704;
}

@media (prefers-color-scheme: dark) {
    .auricula .key-color {
        border: 2px solid #C22FD3;
    }
}

.nextsize .menu-button  {
    border: 2px solid #BE3E30;
}


/* No left margin: the plate's own edge meets the gutter, so it keeps the same distance
   from the screen as the text below it. Its label therefore sits a further 9px in — that
   is the padding, and it is accepted rather than corrected.

   The optical variant was built and shipped on 2026-08-07 and reversed the same day:
   `margin-left: -9px` put the wordmark's first glyph on the gutter and let the dark plate
   overhang into it. On screen the overhang read as the plate being misaligned, which is
   the thing it was supposed to fix. What aligns here is the element, not its type — see
   R-031, which records both attempts. Do not reintroduce the negative margin. */
.sb-button {
    float: left;
    text-align: right;
    margin: 14px 0 12px 0;
    padding: 3px 9px 10px 9px;
    color: #fefefe;
    height: 21px;
    font-size: 24px;
    border-radius: 4px;
    background-color: #333;
}

.title-button {
    float: none;
    margin: 14px auto;
    width: fit-content;
    height: 21px;
    font-size: 24px;
    background-color: transparent;
}

/* Mirror of `.sb-button`: no right margin, so this plate's own edge meets the right
   gutter at the same distance from the screen as the text above and below it. Its label
   sits 8px inside that, which is this rule's padding and is left alone.

   The `margin-right: -8px` optical variant shipped and was reversed on 2026-08-07 for the
   same reason as the wordmark's — see R-031. */
.language-button {
    float: right;
    text-align: right;
    margin: 14px 0 12px 4px;
    padding: 2px 8px 10px 8px;
    height: 21px;
    font-size: 24px;
    border-radius: 4px;
    color: #fefefe;
    background-color: #333 !important;
}

/* Both header plates are inverted against the page: dark plate with light type in
   light mode, and the reverse in dark mode. Neither had a dark-mode rule before —
   they were hardcoded light — so inverting the base alone would have put a black
   plate on the #222 dark background, where it all but disappears. */
@media (prefers-color-scheme: dark) {
    .sb-button {
        color: #333;
        background-color: #fefefe;
    }
    .language-button {
        color: #222;
        background-color: #fefefe !important;
    }
}

.auricula .key-color-dropshadow   {
    box-shadow:  2px 2px 12px 2px #08b704;
}

@media (prefers-color-scheme: dark) {
    .auricula .key-color-dropshadow {
        box-shadow:  2px 2px 12px 2px #C22FD3;
    }
}

.nextsize .key-color-dropshadow   {
    box-shadow:  2px 2px 12px 2px #BE3E30;
}

@media (prefers-color-scheme: dark) {
    .nextsize .key-color-dropshadow {
        box-shadow:  2px 2px 12px 2px #BE3E30;
    }
}

.dropdown-content {
    display: none;
    position: absolute;
    right: 0px;
    top: 58px;
    background-color: #f9f9f9;
    min-width: 140px;
    /*box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .2);*/
    z-index: 1;
    text-align: left
}

@media (prefers-color-scheme: dark) {
    .dropdown-content {
        background-color: #222;
    }
}

.dropdown-content a {
    padding: 12px 16px;
    text-decoration: none;
    display: block
}

.dropdown-content a:hover,
.dropdown:hover .menu-button {
    text-decoration: underline
}

.dropdown:hover .dropdown-content {
    display: block
}

img.profile-picture {
    display: block;
    margin: 18px auto;
    height:200px;
    width:200px;
    border-radius: 100px;
    border: 1px #8c8c8c solid;
}

@media (prefers-color-scheme: dark) {
    img.profile-picture {
        border: 1px #eee solid;
    }
}

@media only screen and (min-width:1024px) {
    img.profile-picture {
        display: block;
        margin: 18px auto;
        height:250px;
        width:250px;
        border-radius: 125px;
    }
}

.new {
    color: #fff;
    height: 21px;
    font-size: 18px;
    padding: 6px 6px 3px;
    border-radius: 4px;
}

@media (prefers-color-scheme: dark) {
    .new {
        color: #333;
    }
}

sup {
    vertical-align: top;
    position: relative;
    top: -12px;
}

.half {
    width: 95%;
}

@media only screen and (min-width:650px) {
    .half {
        width: 45%;
        margin-right: 2%;
    }
}

.article-tile {
    background-color: transparent;
    margin-bottom: 12px;
    border-radius: 4px;
    margin-bottom:48px;
}

@media only screen and (min-width:650px) {
    .article-tile {
        margin-right:24px;
    }
}

.article-tile-frame {
    width: 100%;
    margin-bottom:8px;
    margin-top:4px;
}

.article-tile-frame img {
    width: 100%;
    border-radius: 3px;
}

.square img {
    width: 32%;
}

.section-item {
    display: inline-block;
    float: left;
}

.readable_width {
    margin: 0 auto;
    width:100%;
    max-width: 280px;
} 

@media only screen and (min-width:350px) {
    .readable_width {
        margin: 0 auto;
        width:85%;
        max-width: 600px;
    }
}

.subline {
    width: 80%;
    margin: 0 auto;
}

.header-buttons {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
}

/* The row must not change height between pages. The current section's label is set in
   larger, heavier type than the others, and the landing page has no current section at
   all — so left alone the pills measure 25px everywhere and 68px on the active one, and
   everything below the navigation jumped by 43px as you moved between pages.

   Every pill is given the same line box here, so the row is one fixed height regardless
   of which page it is on — or whether any pill is active at all. STILL LOAD-BEARING
   after R-037: the current label is no longer an h1, but it kept the h1's type, so the
   height difference this corrects is unchanged. */
.header-buttons .pill-button {
    line-height: 38px;
}

/* R-037: the current section's label. Until 2026-08-11 this was an `<h1>`, which is
   where the type below comes from — `h1` is Inclusive Sans Bold at 27px. The pill is
   not a heading and must not follow heading type, so the values are restated here
   rather than inherited: if the site's h1 ever changes size, this must not move with
   it. `margin: 0` and the inherited line-height are what keep the row's fixed height
   working, and were previously carried by a `.header-buttons h1` rule. */
.header-buttons .pill-current {
    /* Block, not the inline span it looks like. The h1 this replaces was block-level, so
       its line box was capped at the inherited 38px however tall the glyphs wanted to be.
       Left inline, a 27px label grows the line box whenever the font's content area
       overflows that 38px — measured 2026-08-11 at 92px against the row's 88px everywhere
       else, which is the same page-to-page jump the rule above exists to prevent, just
       4px of it instead of 43px. */
    display: block;
    font-family: 'Inclusive Sans Bold';
    font-weight: 700;
    font-size: 27px;
    margin: 0;
    line-height: inherit;
}

.newsletter-adress-input {
    font-size: 20px;
    margin: 16px 0;
    outline: 0;
    display: block;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    /*border: 2px solid #3378f8;*/
    padding: 8px 0 8px 12px;
    width: calc(95% - 12px);
    border-radius: 6px;
    box-shadow: none;
}

.screenshots p, .screenshots-old p,
.tag {
    text-align: center;
    display: block
}

.screenshots {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    height: 566px;
    margin-bottom: 36px
}

.screenshots-old {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    height: 466px;
    margin-bottom: 36px
}

.screenshots p, .screenshots-old p {
    margin: 0;
    padding: 0 20px;
    padding-top: 4px;
    padding-bottom: 8px;
    overflow: scroll;
    overflow-y: hidden;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch
}

.screenshots span {
    display: inline-block;
    margin: 0px 10px;
    box-shadow: rgba(0, 0, 0, .5) 0 0 5px;
    border: solid 2px #eee;
    border-radius: 18px;
}

.screenshots-old span {
    display: inline-block;
    margin: 0px 10px;
    box-shadow: rgba(0, 0, 0, .5) 0 0 5px;
    border: solid 2px #333;
    border-radius: 6px;
}

.screenshots-small {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    height: 280px;
    margin-bottom: 18px
}

.screenshots-small p {
    margin: 0;
    padding: 0 0px;
    padding-top: 4px;
    padding-bottom: 8px;
    overflow: scroll;
    overflow-y: hidden;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch
}

.screenshots-small span {
    display: inline-block;
    margin: 0px 10px;
    box-shadow: rgba(0, 0, 0, .5) 0 0 5px;
    border: solid 1px #eee;
    border-radius: 4px;
}

.sc-au-en_1 {
    background: url(../img/projects/auricula/en/auricula_screen_1.jpg) no-repeat;
}

.sc-au-en_2 {
    background: url(../img/projects/auricula/en/auricula_screen_2.jpg) no-repeat;
}

.sc-au-en_3 {
    background: url(../img/projects/auricula/en/auricula_screen_3.jpg) no-repeat;
}

.sc-au-en_4 {
    background: url(../img/projects/auricula/en/auricula_screen_4.jpg) no-repeat;
}


.sc-au-de_1 {
    background: url(../img/projects/auricula/de/auricula_screen_1.jpg) no-repeat;
}

.sc-au-de_2 {
    background: url(../img/projects/auricula/de/auricula_screen_2.jpg) no-repeat;
}

.sc-au-de_3 {
    background: url(../img/projects/auricula/de/auricula_screen_3.jpg) no-repeat;
}

.sc-au-de_4 {
    background: url(../img/projects/auricula/de/auricula_screen_4.jpg) no-repeat;
}


.sc-ns-en_1 {
    background: url(../img/projects/nextsize/en/nextsize_screen_1.jpg) no-repeat;
}

.sc-ns-en_2 {
    background: url(../img/projects/nextsize/en/nextsize_screen_2.jpg) no-repeat;
}

.sc-ns-en_3 {
    background: url(../img/projects/nextsize/en/nextsize_screen_3.jpg) no-repeat;
}

.sc-ns-en_4 {
    background: url(../img/projects/nextsize/en/nextsize_screen_4.jpg) no-repeat;
}


.sc-ns-de_1 {
    background: url(../img/projects/nextsize/de/nextsize_screen_1.jpg) no-repeat;
}

.sc-ns-de_2 {
    background: url(../img/projects/nextsize/de/nextsize_screen_2.jpg) no-repeat;
}

.sc-ns-de_3 {
    background: url(../img/projects/nextsize/de/nextsize_screen_3.jpg) no-repeat;
}

.sc-ns-de_4 {
    background: url(../img/projects/nextsize/de/nextsize_screen_4.jpg) no-repeat;
}

.ps-ns-en_1 {
    background: url(../img/projects/nextsize/en/nextsize_promotional_1.jpg) no-repeat;
}

.ps-ns-en_2 {
    background: url(../img/projects/nextsize/en/nextsize_promotional_2.jpg) no-repeat;
}

.ps-ns-en_3 {
    background: url(../img/projects/nextsize/en/nextsize_promotional_3.jpg) no-repeat;
}

.ps-ns-en_4 {
    background: url(../img/projects/nextsize/en/nextsize_promotional_4.jpg) no-repeat;
}

.ps-ns-de_1 {
    background: url(../img/projects/nextsize/de/nextsize_promotional_1.jpg) no-repeat;
}

.ps-ns-de_2 {
    background: url(../img/projects/nextsize/de/nextsize_promotional_2.jpg) no-repeat;
}

.ps-ns-de_3 {
    background: url(../img/projects/nextsize/de/nextsize_promotional_3.jpg) no-repeat;
}

.ps-ns-de_4 {
    background: url(../img/projects/nextsize/de/nextsize_promotional_4.jpg) no-repeat;
}

.smartphone-portrait {
    background-color: #8c8c8c;
    background-size: 255px 552px;
    width: 255px;
    height: 552px;
    background-size: cover;
}

.smartphone-portrait-5-5 {
    background-color: #8c8c8c;
    background-size: 255px 552px;
    width: 255px;
    height: 453px;
    background-size: cover;
}

@media (prefers-color-scheme: dark) {
    .smartphone-portrait {
        background-color: #bbb;
    }
}

.slider-inset {
    margin-top: 330px;
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .slider-inset {
        margin-top: 400px
    }
}

@media only screen and (min-width:1401px) {
    .slider-inset {
        margin-top: 500px
    }
}

.auricula .slider-inset {
    border-top: 2px solid #08b704;
}

@media (prefers-color-scheme: dark) {
    .auricula .slider-inset {
        border-top: 2px solid #C22FD3
    }
}

.nextsize .slider-inset {
    border-top: 2px solid #BE3E30;
}

@media (prefers-color-scheme: dark) {
    .nextsize .slider-inset {
        border-top: 2px solid #BE3E30;
    }
}

.above-slider {
    position: relative;
    z-index: 2
}

.slider-image {
    position: relative;
    display: block;
    margin: 0 auto;
    padding-top: 80px
}

@media only screen and (min-width:771px) {
    .slider-image {
        padding-top: 100px
    }
}

.slider {
    position: fixed;
    height: 550px;
    background-color: #037d00;
    z-index: 1;
    background-position: center center;
}

.slider img {
    height: 170px;
    width: 145px
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .slider img {
        height: 250px;
        width: 213px
    }
}

@media only screen and (min-width:1401px) {
    .slider img {
        height: 350px;
        width: 300px
    }
}

.auricula .slider {
    background: url(../img/projects/auricula/background.png);
    background-size: 100%
}

@media (prefers-color-scheme: dark) {
    .auricula .slider {
        background: url(../img/projects/auricula/background_dark.png);
        background-size: 100%
    }
}

.nextsize .slider {
    background: url(../img/projects/nextsize/background.png);
    background-size: 100%, 100%;
}

.vimeo {
    position: relative;
    padding-bottom: 70%;
    height: 0;
}
.vimeo iframe {
    position: relative;
    top: 0;
    left: 0;
    max-height:380px;
    width: 100%;
    max-width: 600px;
    margin: 0 auto;
    display: block;
}

@media only screen and (min-width:600px) {

    .vimeo {
        padding-bottom: 380px;
    }
}

.app-icon {
    position: relative;
    display: block;
    margin: 0 auto 25px
}

img.app-icon {
    width: 120px;
    height: 120px;
    border-radius: 27px;
    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
    transition-property: box-shadow;
    transition-duration: .2s;
    transition-timing-function: linear
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    img.app-icon {
        width: 180px;
        height: 180px;
        border-radius: 40px;
        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
        transition-property: box-shadow;
        transition-duration: .2s;
        transition-timing-function: linear
    }
}

@media only screen and (min-width:1401px) {
    img.app-icon {
        width: 250px;
        height: 250px;
        border-radius: 55px;
        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
        transition-property: box-shadow;
        transition-duration: .2s;
        transition-timing-function: linear
    }
}

a img.app-icon:hover {
    box-shadow: 0 4px 22px 0 rgba(0, 0, 0, .2), 0 12px 20px 0 rgba(0, 0, 0, .19)
}

.app-icon-container {
    margin: 40px auto 80px;
    width: 320px;
}

@media only screen and (min-width:771px) {
.app-icon-container {
    margin: 40px auto 80px;
    width: 340px;
    }
}


.slider-icon {
    margin-top: 100px
}

@media only screen and (min-width:771px) and (max-width:1400px) {
    .slider-icon {
        margin-top: 130px
    }
}

@media only screen and (min-width:1401px) {
    .slider-icon {
        margin-top: 140px
    }
}

.download-badge {
    position: relative;
    display: block;
    margin: 0 auto;
    height: 40px;
    width: 135px;
    padding-bottom: 25px
}

.download-badge-container {
    width: 145px;
    margin: 0 auto
}

.new {
    color: #fff;
    height: 21px;
    font-size: 15px;
    padding: 2px 6px;
    border-radius: 4px;
}

.auricula .key-color {
    color: #08b704;
}

@media (prefers-color-scheme: dark) {
    .auricula .key-color {
        color: #C22FD3;
    }
}

.nextsize .key-color {
    color: #BE3E30;
}

@media (prefers-color-scheme: dark) {
    .nextsize .key-color {
        color: #BE3E30;
    }
}

/* Testimonials */

.testimonial_slider {
    height: 420px;
    position: relative;
    overflow: hidden;
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
    -webkit-flex-flow: row nowrap;
    -ms-flex-flow: row nowrap;
          flex-flow: row nowrap;
    -webkit-box-align: end;
    -webkit-align-items: flex-end;
    -ms-flex-align: end;
          align-items: flex-end;
    -webkit-box-pack: center;
    -webkit-justify-content: center;
    -ms-flex-pack: center;
          justify-content: center;
}

@media only screen and (min-width:430px) and (max-width:700px) {

    .testimonial_slider {
        height: 370px;
    }
}

@media only screen and (min-width:701px) and (max-width:770px) {

    .testimonial_slider {
        height: 370px;
    }
}

@media only screen and (min-width:771px) {

    .testimonial_slider {
        height: 330px;
    }
}


.testimonial_slider p {
    font-size: 16px
}

@media only screen and (min-width:771px) {

    .testimonial_slider p {
        font-size: 21px;
        line-height: 28px;
    }
}

.testimonial_slider__nav {
  width: 12px;
  height: 12px;
    margin: 4px 12px;
  border-radius: 50%;
  z-index: 10;
  outline: 6px solid #ccc;
  outline-offset: -6px;
  box-shadow: 0 0 0 0 #333, 0 0 0 0 rgba(51, 51, 51, 0);
  cursor: pointer;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}
.testimonial_slider__nav:checked {
  -webkit-animation: check 0.4s linear forwards;
          animation: check 0.4s linear forwards;
}
.testimonial_slider__nav:checked:nth-of-type(1) ~ .testimonial_slider__inner {
  left: 0%;
}
.testimonial_slider__nav:checked:nth-of-type(2) ~ .testimonial_slider__inner {
  left: -100%;
}
.testimonial_slider__nav:checked:nth-of-type(3) ~ .testimonial_slider__inner {
  left: -200%;
}
.testimonial_slider__nav:checked:nth-of-type(4) ~ .testimonial_slider__inner {
  left: -300%;
}
.testimonial_slider__inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  -webkit-transition: left 0.4s;
  transition: left 0.4s;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-flex-flow: row nowrap;
      -ms-flex-flow: row nowrap;
          flex-flow: row nowrap;
}

.testimonial_slider__contents {
  height: auto;
  padding: 2rem;
  text-align: center;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-flex: 1;
  -webkit-flex: 1;
      -ms-flex: 1;
          flex: 1;
  -webkit-flex-flow: column nowrap;
      -ms-flex-flow: column nowrap;
          flex-flow: column nowrap;
  -webkit-box-align: center;
  -webkit-align-items: center;
      -ms-flex-align: center;
          align-items: center;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
      -ms-flex-pack: center;
          justify-content: center;
    background-color: transparent;
    border-radius: 12px;
    margin-left: 1%;
    margin-right: 1%;
    margin-top: 5px;
    margin-bottom: 15px;
    border-color: #08b704;
    border-width: 1px;
    border-style: solid;
}

@media (prefers-color-scheme: dark) {
    .testimonial_slider__contents {
        border-color: #C22FD3;
        background-color: #3c3c3c;
    }
}

.testimonial_slider__caption {
  margin: 2rem 0 1rem;
}
.testimonial_slider__txt {
  max-width: 300px;
}
@media only screen and (min-width:771px) {
    .testimonial_slider__txt {
      max-width: 420px;
    }
}
@-webkit-keyframes check {
  50% {
    outline-color: #333;
    box-shadow: 0 0 0 12px #333, 0 0 0 36px rgba(51, 51, 51, 0.2);
  }
  100% {
    outline-color: #e6ab66;
    box-shadow: 0 0 0 0 #333, 0 0 0 0 rgba(51, 51, 51, 0);
  }
}

@keyframes check {
  50% {
    outline-color: #333;
    box-shadow: 0 0 0 12px #e6ab66, 0 0 0 36px rgba(51, 51, 51, 0.2);
  }
  100% {
    outline-color: #08b704;
    box-shadow: 0 0 0 0 #e6ab66, 0 0 0 0 rgba(51, 51, 51, 0);
  }
  @media (prefers-color-scheme: dark) {

  }
}

@keyframes check {
    50% {
        outline-color: #333;
        box-shadow: 0 0 0 12px #e6ab66, 0 0 0 36px rgba(51, 51, 51, 0.2);
      }
      100% {
        outline-color: #C22FD3;
        box-shadow: 0 0 0 0 #e6ab66, 0 0 0 0 rgba(51, 51, 51, 0);
      }
    }
/* A stray closing brace stood here until 2026-08-11 — this block had four closes for
   three opens. It cancelled out against the unclosed media query at the end of the file,
   so a naive brace count balanced and neither fault was visible. */

.light {
    display: block;
}

.dark {
    display: none;
}

@media (prefers-color-scheme: dark) {
    .light {
        display: none;
    }

    .dark {
        display: block;
    }
}

.text-slider {
    color:#ddd; 
}

@media (prefers-color-scheme: dark) {
    .text-slider {
        color:#333; 
    }
}

.text-slider h3 {
    font-size: 20px;
    line-height: 44px;
    padding-top: 65px
}

@media only screen and (min-width:771px) and (max-width:1399px) {
    .text-slider h3 {
        font-size: 38px;
        line-height: 70px;
        padding-top:80px
    }
}

@media only screen and (min-width:1400px) {
    .text-slider h3 {
        font-size: 42px;
        line-height: 85px;
        padding-top:90px
    }
}

.screenshot-screen-portrait {
    background-color: #8c8c8c;
    background-size: 255px 552px;
    width: 151px;
    height: 271px;
    background-size: cover;
}

@media (prefers-color-scheme: dark) {
    .screenshot-screen-portrait {
        background-color: #bbb;
    }
}

.word-left > span,
.word-right > span {
  animation: show 3s infinite;
}

.word-left > span:nth-child(2) { animation-delay: 3s; }
.word-left > span:nth-child(3) { animation-delay: 3s; }

.word-right > span {
  animation-duration: 4s;
}

.word-right > span:nth-child(2) { animation-delay: 3s; }

@keyframes show {
  0% { display: none; }
  50% { display: inline-block; }  
  100% { display: none; }
}

.image-grid img {
    position: relative;
    display: block;
    float:left;
    text-align: center;
    width:320px;
    height:320px;
    margin-bottom:40px;
    margin-right:20px;
    margin-left:20px;
    left:0px;
}

@media only screen and (min-width:771px) {
    .image-grid img {
    position: relative;
    display: block;
    float:left;
    text-align: center;
    width:260px;
    height:260px;
    margin-bottom:40px;
    margin-right:20px;
    margin-left:20px;
    left:0px;
    }
}
/* ^ That second brace closes the min-width:771px query. It was MISSING until 2026-08-11:
   the file simply ended mid-query, with no trailing newline. Browsers auto-close open
   blocks at EOF so nothing ever looked wrong — but every rule appended to this file
   landed silently inside that media query and applied only above 771px. */

/* App screenshots on a product page. The old site drew these as background-images on
   empty spans; here they are real <img> elements, so they can be lazy and can carry alt
   text. The row scrolls sideways rather than wrapping — four portrait shots wrap to two
   ragged rows at most widths, and a strip that runs off the edge reads as "there is more"
   in a way a broken grid does not.

   `flex-shrink: 0` is load-bearing: without it the flex container squeezes all four to
   fit and nothing scrolls, which looks like the images are the wrong size. */
.app-screenshots {
    display: flex;
    gap: 20px;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 12px;
    margin-bottom: 24px;
}

.app-screenshots img {
    flex-shrink: 0;
    display: block;
    width: 255px;
    height: 453px;
    border-radius: 6px;
    border: solid 2px #333;
}

@media (prefers-color-scheme: dark) {
    .app-screenshots img {
        border-color: #eee;
    }
}

/* The App Store badge. inline-block on the wrapper, not on the img, for the reason
   `.action-button` is built this way (R-036): `a:hover` sets text-decoration: underline,
   which propagates from the anchor into ordinary block descendants and cannot be turned
   off from inside them. An atomic inline is never entered, so no line is drawn. */
.appstore-badge {
    display: inline-block;
}

.appstore-badge img {
    display: block;
    width: 135px;
    height: 40px;
}
