/* ============================================================================
   annotepage.com -- the tokens every page shares, and nothing else.

   WHY THIS FILE EXISTS, AND WHY IT IS SMALL. The pages are written by hand,
   with no build step: the file you read is the file that is served. That is
   worth keeping, and it costs one thing -- the frame around the content is
   copied rather than shared, and copies drift.

   Measured before this file existed: of roughly 2 800 lines of style across
   the site, 97 were identical everywhere, and 33 of those were these tokens.
   The rest is genuinely different work -- one page draws screenshots and a
   terminal, another draws a replay, another draws code blocks. Extracting THAT
   would be inventing a framework for three pages that do not want one.

   But these are the lines whose drift is invisible and fatal: an --accent that
   differs by one page is a broken site that looks fine on every page taken
   alone. So they live once, here, and each page keeps only the tokens it alone
   uses.

   THE RULE FOR THE COLOURS, and it is not negotiable. Every colour is defined
   on bare `:root`, and the dark block only REDEFINES. A colour that exists in
   one scheme and not the other is a page that renders in one of them.

   HOW TO USE THEM, since the point of a shared file is that a second page can
   reuse the first one's work:

     background: var(--bg);          the page's own ground
     background: var(--bg-soft);     a panel, a note, an inactive chip
     background: var(--bg-code);     anything holding code
     color: var(--text);             what is read
     color: var(--dim);              what is read second: captions, asides
     border: 1px solid var(--line);  a boundary meant to be SEEN
     border: 1px solid var(--line-soft);  one meant to be felt
     color: var(--accent);           a link, a control, the chosen thing
     color: var(--on-accent);        text ON --accent, never anywhere else
     --warn / --bg-warn              a cost the reader has to know about
     --ok / --ok-soft                something that succeeded, and stays quiet
     box-shadow: var(--shadow);      one elevation, so panels agree
     border-radius: var(--radius);   one corner, same reason
     max-width: var(--measure);      the reading width. Pictures ignore it.

   A NEW TOKEN GOES HERE ONLY IF TWO PAGES USE IT. One page's colour belongs in
   that page, next to the thing it paints and the reason it is that colour --
   which is how --accent-soft stayed on the landing page, and how the code
   blocks' ink stayed there with the only chapter that holds code.

   AND THE SAME RULE MOVED --shot-shadow AND THE TERMINAL INK HERE, the day a
   second page drew a screenshot and a command line. They are further down,
   with the pieces they paint.
   ============================================================================ */

:root {

    --bg:        #ffffff;
    --bg-soft:   #f5f6f8;
    --bg-code:   #f1f2f5;
    --bg-warn:   #fdf6e9;
    --text:      #16181d;
    --dim:       #52565f;
    --line:      #d3d7de;
    --line-soft: #e2e5ea;
    /* THE BOUNDARY OF AN INTERACTIVE CONTROL, which --line cannot serve. It
       left this file once, when the only control that needed it was a copy
       button that had moved onto a surface not following the theme; it comes
       back because the replay's own control is here now, and TWO pages carry
       one. Measured on the rendered pixels: --line reaches 1.34:1 against the
       strip that control sits on, where WCAG 1.4.11 asks for 3. This one
       measures 3.35:1 against that strip and 3.62:1 against the button's own
       fill -- the strip is --bg-soft, defined in this file, so the numbers
       hold on every page that links it. It is deliberately NOT applied to the
       frames around the pictures: those are decoration, and 1.4.11 does not
       cover them. */
    --control-line: #7f8794;
    --accent:    #0b53c0;
    --on-accent: #ffffff;
    --warn:      #7a4200;
    --ok:        #0a6b3d;
    /* The fill behind a resolved stamp. Defined here, on bare :root, and only
       REDEFINED in the dark block -- like every other colour on this page. */
    --ok-soft:   #e6f4ea;
    --shadow:    0 1px 2px rgba(16, 18, 24, .06), 0 8px 28px rgba(16, 18, 24, .07);
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
            "DejaVu Sans Mono", monospace;
    --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
            Arial, "Noto Sans", sans-serif;
    --radius: 10px;
    /* THE READING MEASURE, and it is not the page's width -- see .wrap. */
    --measure: 44rem;
}

@media (prefers-color-scheme: dark) {
    :root {
            --bg:        #0f1115;
            --bg-soft:   #171a20;
            --bg-code:   #13161b;
            --bg-warn:   #1d1810;
            --text:      #e6e8ec;
            --dim:       #a3a9b4;
            --line:      #38404c;
            --line-soft: #262c35;
            --control-line: #6a7484;
            --accent:    #8ab4ff;
            --on-accent: #0f1115;
            --warn:      #f2c17a;
            --ok:        #5fd39a;
            --ok-soft:   #17291f;
            --shadow:    0 1px 2px rgba(0, 0, 0, .5), 0 8px 28px rgba(0, 0, 0, .45);
    }
}


/* -- THE TERMINAL INK, PINNED DARK IN BOTH PAGE THEMES --------------------

   The one group in this file that is NOT redefined in the dark block, and the
   exception is deliberate rather than forgotten. A terminal on a developer's
   machine is a dark ground with light text; drawn on white it stops reading as
   a screen and starts reading as a web page dressed up as one. So it keeps its
   own ink, and the ink is the DARK column of the theme table taken out of the
   client being reproduced. Nothing here is invented.

   Measured on rendered pixels against the #1a1a1a ground, and the figures
   cannot differ between page themes because nothing painted on this ground
   depends on one: white text 17.40:1, dim #999 6.11:1, the spinner accent
   #d77757 5.52:1. The pair of rules round the input is #888 at 4.91:1, which
   is a line and not text.

   What a REPLAY needs on top of these -- the tool bullet, the success green,
   the band behind a sent line -- belongs to the page that replays, and stays
   there. */
:root {
    --t-ground:  #1a1a1a;   /* not #000: a pure black ground is the tell */
    --t-text:    #ffffff;
    --t-border:  #888888;
    --t-assistant:  #d77757;
    --t-dim:     #999999;
    /* AND WHAT THE WINDOW FIGURE NEEDS, which is not the replay. It moved here
       with the figure it paints -- the editor's title band and copy chip, the
       identifier tint in the file, the tick when a copy lands, the shimmer
       when it does not. Measured on the ground above: --t-chrome carries white
       at 14.16:1 and --t-dim at 4.97:1; on --t-ground, #4eba65 is 7.07:1,
       #a4cbfa 10.36:1, #eb9f7f 8.12:1. */
    --t-chrome:  #2b2b2b;
    --t-ident:   #a4cbfa;
    --t-ok:      #4eba65;
    --t-shimmer: #eb9f7f;
}

/* -- AND WHAT MAKES A FRAME READ AS A PHOTOGRAPH --------------------------

   Heavier than --shadow, and it is not a second opinion about the same thing:
   --shadow lifts a pill a millimetre off the page it floats on, this one has
   to make a flat rectangle read as a PHOTOGRAPH of another screen. The near
   layer draws the edge, the far one is offset down and pulled in with a
   negative spread so the picture sits ON the page rather than glowing around
   it. Two pages mount a screenshot now, so it lives here. */
:root {
    --shot-shadow: 0 2px 4px rgba(16, 18, 24, .08),
                   0 18px 40px -12px rgba(16, 18, 24, .28);
}
@media (prefers-color-scheme: dark) {
    :root {
        /* A shadow on a dark ground carries almost nothing; what separates
           the picture from the page there is the mat and the border. This
           still deepens the seam under it, and is the darkest ink available
           rather than a tint of the ground, which would lighten it. */
        --shot-shadow: 0 2px 4px rgba(0, 0, 0, .55),
                       0 18px 40px -12px rgba(0, 0, 0, .8);
    }
}


/* ============================================================================
   THE DRAWN INTERFACE -- one set of pieces, used by more than one page.

   WHY IT IS HERE AND NOT IN A PAGE. The rule at the top of this file is that a
   token moves here once two pages use it, and the same rule decides a
   component: the landing page's first chapter and how-to-use-it.html draw
   the SAME things -- a crop of somebody's page, the tool's floating button,
   the rectangle round the element being pointed at, the note form, the panel
   of remarks, the crop of a command line. They were written once, on the
   landing page, and the second page was about to copy them. A copy of a
   drawing drifts exactly the way a copy of a colour does, and it drifts
   invisibly: two pages showing the same interface slightly differently is a
   site that is lying about one of them.

   So the drawing lives once. What stays in a page is where the drawing SITS --
   the grid it is laid out on, what is hidden at which width, the mat that
   makes it read as a photograph. That is layout, and it is genuinely
   different work from one page to the next.

   NOTHING HERE IS INVENTED. Every piece reproduces something the client
   actually draws, and every string that goes in one is a string of
   client/src/15-labels.js. The drawing is ours; the interface is not.

   -- THE ONE DIAL -----------------------------------------------------------

   The two pages want the same pieces at two sizes: the landing page shows
   them small, beside a sentence, three to a chapter; the session page shows
   one at a time, large enough to read a note in. That is a SIZE, not a second
   component, so it is a size: `--ui` is a plain multiplier, and every length
   in this file is that multiplier times the number the drawing was designed
   at.

   Unitless on purpose. A length would have to be divided back out of every
   rule and would change what `.42rem` means to whoever reads it; a multiplier
   leaves the original numbers on the page, where they can still be compared
   with the measurements that produced them.

   Three densities, and the middle one is 1 -- which is the landing page's,
   unchanged to the pixel. That is not a coincidence, it is the point: the
   page that already renders correctly is the reference, so it carries no
   class at all and `calc(.6rem * 1)` is `.6rem`. Verified by comparing the
   computed style of every element of that page before and after this file
   gained these rules, in both themes at three widths: zero differences.

   Border widths are NOT scaled. A hairline is a hairline at any size, and a
   3px accent rule that grows into a 5px slab stops reading as the same
   drawing.
   ============================================================================ */

:root { --ui: 1; }
/* Named for what they are rather than for where they are used, so a third
   page does not have to know which one the session page took. */
.ui-tight  { --ui: .82; }
.ui-medium { --ui: 1; }
.ui-large  { --ui: 1.45; }

/* -- the frame, and the pieces inside it ---------------------------------- */

/* The frame is the crop: a piece of somebody's page, or of a command line,
   with the tool drawn on it. */
.frame {
    position: relative;
    /* min-height and NOT height: a fixed height was measured at the default
       text size only, and at a 200% text size the content wraps to more lines
       -- and was cut off with no scrollbar. The floor still aligns frames
       whenever they sit side by side; below that they are stacked in one
       column, where nothing has to line up with anything. */
    min-height: calc(7.5rem * var(--ui));
    border: 1px solid var(--line-soft);
    border-radius: calc(8px * var(--ui));
    background: var(--bg);
    overflow: hidden;
    padding: calc(.6rem * var(--ui));
}
/* fake page furniture behind the demonstration */
.bar-line { height: calc(.42rem * var(--ui)); border-radius: calc(3px * var(--ui)); background: var(--line-soft); }
.bar-line + .bar-line { margin-top: calc(.38rem * var(--ui)); }
.bar-line.w70 { width: 70%; } .bar-line.w45 { width: 45%; } .bar-line.w85 { width: 85%; }
/* A line of the page BELOW an annotated row. `.bar-line + .bar-line` cannot
   reach it -- the row of elements sits between them -- so its gap is stated. */
.bar-line.below { margin-top: calc(1rem * var(--ui)); }
/* A heading of the page, in the same grey shorthand: taller, shorter across,
   the firmer of the two lines, and with a paragraph's worth of air under it.
   Still the HOST's content and not ours -- what a page puts in its headings is
   the business of whoever installs the tool. */
/* Its air is its OWN margin and not the next bar's. A heading that is hidden
   at some width must take its spacing with it, and `.bar-line.head + .bar-line`
   would have left the gap behind: the adjacent-sibling combinator does not
   care that the element it matched is display:none. */
.bar-line.head { height: calc(.62rem * var(--ui)); width: 34%; background: var(--line); margin-bottom: calc(.8rem * var(--ui)); }
.bar-line + .bar-line.head { margin-top: calc(1.4rem * var(--ui)); }

.pill {
    position: absolute; right: calc(.55rem * var(--ui)); bottom: calc(.55rem * var(--ui));
    /* A dot and a label, and that is the whole row: the min-content of a flex
       row is the SUM of its items and it wins over max-width, so a row of two
       items -- one of them a label free to wrap inside itself -- can never
       push the pill past the edge of a frame that clips. It could when a
       count badge made a third: at a 200% text size that came out 41px past
       the frame, and "3 notes" read "3 no". */
    display: inline-flex; align-items: center; gap: calc(.4rem * var(--ui));
    /* At rest the real button is a light pill with one accent dot, not a slab
       of accent: it sits on somebody else's page and must not shout. */
    background: var(--bg); color: var(--text); border: 1px solid var(--line);
    /* 1rem and not 999px: the browser clamps the radius to half the height,
       so at one line (28px tall, measured) this is still a true pill, while a
       999px radius turns a three-line box into an ellipse once the text is
       enlarged. Same shape where it matters, no blob where it does not. */
    border-radius: calc(1rem * var(--ui)); padding: calc(.3rem * var(--ui)) calc(.7rem * var(--ui));
    font-size: calc(.72rem * var(--ui)); font-weight: 650;
    box-shadow: var(--shadow);
    max-width: calc(100% - 1.1rem * var(--ui));
}
.pill .dot { width: calc(.42rem * var(--ui)); height: calc(.42rem * var(--ui)); border-radius: 50%; background: var(--accent); flex: none; }

/* The rectangle is drawn AROUND the target, never on it -- which is also what
   the tool does: it never puts an outline on an element of the host page. */
/* inline-FLEX and not inline-block: a flex container drops the whitespace-only
   text nodes the indentation leaves inside these wrappers, so the wrapper is
   exactly as tall as the button it holds. As inline-blocks they carried a line
   box's strut and an annotated pair started six pixels lower than a fixed pair
   in the frame beside it -- measured -- which is the one difference between
   two crops of one page that must not exist. */
.target { position: relative; display: inline-flex; max-width: 100%; }
.target::after {
    content: ""; position: absolute; inset: calc(-5px * var(--ui));
    border: 2px solid var(--accent); border-radius: calc(8px * var(--ui));
    pointer-events: none;
}
/* THE TWO BUTTONS OF THE PAGE UNDER REVIEW, AND THEY ARE THE MEASUREMENT.
   The two states are one element before and after the edit, and nothing else
   about them differs: 36px of horizontal padding with a 17px label, then 16px
   and 16px. One defect, drawn the same way wherever this site draws it.

   --ok is the site's green and --on-accent the ink that always answers a
   filled surface here: white on #0a6b3d is 6.51:1, near-black on #5fd39a is
   10.16:1, both measured. The host page's own green is not reproduced -- what
   colour somebody's buttons are is their business; that they wrap is ours. */
.gbs {
    /* 1.3rem between the two, and it is the pointed element that asks for it:
       its rectangle is drawn 5px outside its own edge and the excerpt hangs
       .3rem further left again, so a narrower gap would put a piece of the
       tool on the button beside it. The same number in every frame, because
       two crops of one page have to be the same row. */
    display: flex; flex-wrap: wrap; gap: calc(1.3rem * var(--ui));
    /* flex-start on both, so a row that wraps keeps the height its own padding
       gives it: stretched, the 16px pair would be drawn as tall as the 36px
       pair it replaced, and half of what the edit did is that height. */
    align-items: flex-start; align-content: flex-start;
    /* Room above for the floating excerpt, which hangs off the top of the
       element it quotes; and .55rem on the left for the rectangle and that
       excerpt, drawn 5px and .3rem outside the element's own left edge, which
       would otherwise sit on the frame's padding. Stated here rather than on
       .target, so that two rows -- one with those pieces, one without -- start
       at the same x and the two frames really are the same crop. */
    margin: calc(1.9rem * var(--ui)) 0 0; padding-left: calc(.55rem * var(--ui));
}
.gb {
    display: inline-block; text-align: center; border-radius: calc(6px * var(--ui));
    background: var(--ok); color: var(--on-accent);
    font-size: calc(.72rem * var(--ui)); font-weight: 650; line-height: 1.35;
}
/* The break is forced with <br>, not obtained from a width: a width holds only
   until a root font-size or a viewport moves, and on a tablet it did move --
   the label sat on ONE line while the note beside it said it wrapped onto two.
   An illustration that contradicts its own caption is worse than none. */
.gbs-before .gb { padding: calc(.5rem * var(--ui)) calc(1.5rem * var(--ui)); }
.gbs-after  .gb { padding: calc(.5rem * var(--ui)) calc(.7rem * var(--ui)); }
/* Above the element, never on it: the tool floats the text it is about to
   quote, so the reviewer sees what the note will say it is about. */
.marker {
    position: absolute; left: calc(-.3rem * var(--ui)); bottom: calc(100% + .45rem * var(--ui));
    background: var(--accent); color: var(--on-accent);
    font-size: calc(.62rem * var(--ui)); font-weight: 650; line-height: 1.75;
    border-radius: calc(5px * var(--ui)); padding: 0 calc(.4rem * var(--ui)); white-space: nowrap;
}

/* -- a crop of the command line, dark in both themes ----------------------

   The two rules around the input and the indicator above it are the two
   pieces of chrome the tool actually draws; nothing else is in here. */
.frame.term {
    background: var(--t-ground); border-color: var(--t-border);
    color: var(--t-text);
    font-family: var(--mono); font-size: calc(.74rem * var(--ui)); line-height: 1.55;
    padding: calc(.85rem * var(--ui)) calc(.8rem * var(--ui));
    display: flex; flex-direction: column; justify-content: center; gap: calc(.5rem * var(--ui));
}
.mt-work { margin: 0; color: var(--t-assistant); }
.mt-work .g { display: inline-block; width: 2ch; }
.mt-input {
    border-top: 1px solid var(--t-border);
    border-bottom: 1px solid var(--t-border);
    padding: calc(.5rem * var(--ui)) 0; color: var(--t-text);
}
.mt-input .ps { color: var(--t-dim); margin-right: .6ch; }

/* THE ONE MOVING THING IN A TERMINAL CROP, and it is one character wide. A
   command line that is being typed into has a cursor on it; without one the
   frame is a photograph of a terminal, and the step it illustrates is the
   step where something is happening.

   steps(1) and not a fade: a terminal cursor is on or off, and an easing curve
   would be a web animation wearing a terminal's clothes. 1.06s is the interval
   the cursor of a VT-style terminal keeps.

   REDUCED MOTION IS ANSWERED BY THE PAGE, which drops every animation -- and
   the answer is the right one rather than merely an absence: with no animation
   the span keeps its static state, which is a solid block sitting after the
   last character. The line still reads as a prompt being typed into; it simply
   stops blinking. */
.mt-input .cur {
    display: inline-block; width: 1ch; height: 1.05em;
    vertical-align: -.18em; margin-left: .35ch;
    background: var(--t-text);
    animation: term-cursor 1.06s steps(1) infinite;
}
@keyframes term-cursor { 50% { opacity: 0; } }

/* A text caret rather than the block the terminal uses: a caret in a field is
   a hairline, and a block there would be a terminal drawn inside a web form.
   It borrows the terminal's interval and its steps(1), because a caret does
   not fade. */
.caret {
    display: inline-block; width: 1px; height: 1.05em;
    vertical-align: -.18em; margin-left: .5px;
    background: var(--text);
    animation: term-cursor 1.06s steps(1) infinite;
}

/* -- the panel of remarks, as the reviewer finds it ------------------------

   In the page's own colours: this is a drawing, not a capture, so it follows
   the reader's theme like everything else around it. */
.nt-panel {
    border: 1px solid var(--line-soft); border-radius: calc(8px * var(--ui));
    overflow: hidden; background: var(--bg);
}
.nt-bar {
    display: flex; align-items: center; gap: calc(.4rem * var(--ui));
    padding: calc(.35rem * var(--ui)) calc(.6rem * var(--ui)); background: var(--bg-soft);
    border-bottom: 1px solid var(--line-soft);
    font-size: calc(.7rem * var(--ui)); font-weight: 650;
}
/* --ok on --ok-soft: 5.81:1 light, 8.21:1 dark, both measured. */
.nt-count {
    margin-left: auto; background: var(--ok-soft); color: var(--ok);
    border-radius: 999px; padding: 0 calc(.45rem * var(--ui)); font-size: calc(.64rem * var(--ui)); font-weight: 650;
}
.nt-body { padding: calc(.5rem * var(--ui)); display: grid; gap: calc(.45rem * var(--ui)); }
.nt {
    border: 1px solid var(--line-soft); border-left: 3px solid var(--ok);
    border-radius: calc(6px * var(--ui)); padding: calc(.42rem * var(--ui)) calc(.5rem * var(--ui));
}
.nt p { margin: calc(.2rem * var(--ui)) 0 0; font-size: calc(.68rem * var(--ui)); }
.nt p.nt-who { margin: 0; font-size: calc(.66rem * var(--ui)); color: var(--dim); }
.nt p.nt-who b { color: var(--text); font-weight: 650; }
.nt p.nt-about {
    margin: calc(.2rem * var(--ui)) 0 0; font-size: calc(.64rem * var(--ui)); color: var(--dim);
    border-left: 2px solid var(--line-soft); padding-left: calc(.4rem * var(--ui));
}
.nt-stamp {
    display: block; margin: calc(-.42rem * var(--ui)) calc(-.5rem * var(--ui)) calc(.35rem * var(--ui)); padding: calc(.22rem * var(--ui)) calc(.5rem * var(--ui));
    background: var(--ok-soft); color: var(--ok);
    font-size: calc(.64rem * var(--ui)); font-weight: 650;
}
.nt.reply { margin-left: calc(.7rem * var(--ui)); border-left: 2px solid var(--line-soft); }
/* THE THREE STATES A REMARK HAS ON A CARD, and the colours are the client's,
   not a decoration.

   ANSWERED AND ONLINE takes the success ink, and it is the default here
   because it is the only state this site drew before there was a second page
   -- every frame in "How you use it" shows a thread that is finished.

   NOT YET ANSWERED takes no colour at all: the client draws a plain card, and
   a green rule down the side of a remark nobody has looked at would be the
   figure claiming something.

   RESOLVED BUT NOT DEPLOYED takes the WARNING ink, in the client (an amber
   mark, not the green one) and here. It is not a success yet: the defect the
   remark is about is still on the reviewer's screen, and the whole reason the
   tool distinguishes the two states is that she must not be told otherwise.
   --warn on --bg-warn is the pair this site already uses for a cost the reader
   has to know about. */
.nt.nt-open { border-left-color: var(--line); }
.nt.nt-wait { border-left-color: var(--warn); }
.nt-stamp.nt-wait { background: var(--bg-warn); color: var(--warn); }

/* -- the same dock, with a note being written into it ----------------------

   The reviewer's form, and it is the form and not the list: the client anchors
   `.ap-form` beside the element just clicked, a card of its own with a shadow,
   while the panel is the standing list. Both live in the same place on the
   screen -- the right-hand side, at the width the tool gives them -- which is
   why a frame can dock either on the same edge and the crop reads as one crop.

   Everything in it is a string of the client: `form.title`, `form.about`,
   `author.known` and `author.change`, `form.send`, `form.cancel`, and
   `form.remaining` counting down from the 4000 the tool allows. The outline is
   the field's own focus ring -- somebody is typing in it -- and it is an
   outline rather than a border because a ring costs no layout and cannot push
   anything off the side of a frame that clips. */
.nt-form { border-color: var(--line); box-shadow: var(--shadow); padding: calc(.6rem * var(--ui)); }
.fm-title { margin: 0; font-size: calc(.74rem * var(--ui)); font-weight: 650; }
.fm-about {
    margin: calc(.3rem * var(--ui)) 0 0; font-size: calc(.64rem * var(--ui)); color: var(--dim); font-style: italic;
    border-left: 2px solid var(--line-soft); padding-left: calc(.4rem * var(--ui));
}
/* The composer names its author so the reader recognises the same person in
   the thread further on. One line, not the client's full
   "You are writing as X. Change". */
.fm-sign { margin: calc(.1rem * var(--ui)) 0 calc(.45rem * var(--ui)); font-size: calc(.78rem * var(--ui)); color: var(--dim); }
.fm-area {
    margin: calc(.45rem * var(--ui)) 0 0; min-height: calc(3.2rem * var(--ui));
    border: 1px solid var(--line); border-radius: calc(6px * var(--ui));
    background: var(--bg-soft); padding: calc(.35rem * var(--ui)) calc(.45rem * var(--ui));
    font-size: calc(.68rem * var(--ui)); line-height: 1.5;
    outline: 2px solid var(--accent); outline-offset: 1px;
}
.fm-acts {
    margin: calc(.6rem * var(--ui)) 0 0; display: flex; align-items: center; flex-wrap: wrap;
    gap: calc(.4rem * var(--ui)); font-size: calc(.64rem * var(--ui));
}

/* -- and the same form asking for the project key ------------------------
   Two lines the note form does not have, because the client's panel has
   them here: the sentence that explains why it is asking, and the field's
   label. The key itself takes .fm-area -- it is the same field, with the
   same focus ring, and what is in it is 43 characters of base64 rather
   than a sentence, so it is set in the mono face and allowed to wrap. */
.fm-help { margin: calc(.35rem * var(--ui)) 0 0; font-size: calc(.64rem * var(--ui)); color: var(--dim); line-height: 1.5; }
.fm-label { margin: calc(.55rem * var(--ui)) 0 0; font-size: calc(.62rem * var(--ui)); font-weight: 650; }
.fm-key {
    font-family: var(--mono); font-size: calc(.64rem * var(--ui));
    min-height: 0; overflow-wrap: anywhere; white-space: normal;
}
.fm-send {
    background: var(--accent); color: var(--on-accent);
    border-radius: calc(6px * var(--ui)); padding: calc(.2rem * var(--ui)) calc(.6rem * var(--ui)); font-weight: 650;
}


/* -- A NUMBERED STEP ------------------------------------------------------

   A number, a caption on one line, and one sentence under it, beside the
   thing being shown. It is the shape "How you use it" and "How you install
   it" are both written in, and it is the shape the detailed example is
   written in too -- so the caption, the number and the sentence live here and
   the GRID they sit on stays with each page, which is the part that really
   differs: three small frames in a chapter, three large ones on a page of
   their own.

   AND A FOURTH DRAWING CONFIRMED THE SPLIT RATHER THAN CHANGING IT.
   how-to-install-it.html now writes its three chapters in this shape and adds
   nothing to what is above: it took the caption, the number and the sentence
   from here unmodified. What it could NOT take is the grid, and the reason is
   the same one that left it out in the first place -- the landing page folds
   at 48rem into a fraction and again at 78rem into 21rem of words; the
   detailed example folds once at 62rem into 18rem; the install page folds once
   at 62rem too but tops its rows rather than centring them, because a code
   block's first line is its first pixel and a capture's is not. Three grids,
   three arguments, none of them shared by two pages. Moving any one of them
   here would only mean the other two overriding it further down.

   The counting is in the same case and for the same reason. The number is
   drawn here; WHERE THE COUNT STARTS is not, because that is the one thing the
   three disagree about -- the detailed example resets once for a walk that
   runs 01 to 05 across three chapters, and the install page resets at every
   chapter so that no number is ever a tally of things to install.

   Scoped to `.use-steps` and not loose, because `.cap`, `.n` and `.say` are
   words that would collide with anything. */
.use-steps .cap {
    margin: 0; display: flex; align-items: baseline; gap: .6rem;
    font-size: clamp(1.06rem, 2.5vw, 1.24rem); font-weight: 650;
    line-height: 1.3; letter-spacing: -0.01em;
}
.use-steps .cap .n {
    flex: none;
    font: 650 .78rem/1 var(--mono); color: var(--accent);
}
.use-steps .say { margin: .55rem 0 0; color: var(--dim); font-size: .95rem; max-width: 30em; }

/* -- the page crop with the tool docked on its edge -------------------------

   Two pages draw this now -- the landing page's first chapter and the usage
   page's steps -- so it lives here rather than in whichever one wrote it
   first. It was in the landing page until the usage page needed it, and the
   panel simply stacked below the crop over there, which is not what the tool
   does at that width.

   STACKED IS NOT A FALLBACK, IT IS WHAT THE TOOL DOES. Below 560px the
   client's own panel leaves the right-hand edge and becomes a band under the
   page, so that the page above it can still be pointed at. The gap is stated
   because neither of the two carries a margin of its own. */
.nt-frame .nt-panel { margin-top: calc(.9rem * var(--ui)); }

@media (min-width: 78rem) {
    .nt-frame { display: flex; align-items: flex-start; gap: calc(1.4rem * var(--ui)); }
    .nt-frame .nt-panel { margin-top: 0; }
    /* THE PANEL SHRINKS BEFORE IT LEAVES THE FRAME. `flex: 0 0 21.5rem` held
       its width against everything, and at a 200% text size it had to: the
       sentence column is 21rem of a bigger rem then, so the frame comes out
       604px and a 688px panel hung out of a box with overflow:hidden. The
       basis is what it should be at the reading size and the panel gives it up
       when there is not enough, with the page beside it giving up the larger
       share -- 8rem of basis against 21.5. Measured at 200%: 378px of panel and
       140px of page, both inside the frame. */
    .nt-frame .pg { flex: 1 1 8rem; min-width: 0; }
    .nt-frame .nt-panel { flex: 0 1 calc(21.5rem * var(--ui)); min-width: 0; }
}


/* -- THE BAR AT THE TOP OF EVERY PAGE ------------------------------------
   IT LIVED IN ALL THREE PAGES, copied by hand, and two of the copies had lost
   the `.leave` rule -- so the GitHub link sat 17.6px closer to its neighbours
   on the two pages nobody re-read. The HTML of the header is compared by
   tools/check-shell.mjs; its CSS was not, and could not be while each page
   carried its own. It is here now, once, and that is the check. */

/* -- Top bar ------------------------------------------------------------- */

/* IT STAYS AT THE TOP. The site is four pages of long prose and the way
   between them was three screens back up; the menu is the one thing on the page
   that is useful at every scroll position.

   z-index 10, and deliberately small: the annotated page carries the tool
   itself, which draws in a shadow root high above everything here. A header
   that outbid it would sit over the panel a reviewer is writing in.

   AND THE ANCHORS MOVE DOWN BY WHAT THE BAR TAKES. Without scroll-margin-top,
   `/#use` puts its heading exactly where the bar now is, so the reader lands on
   a chapter whose title they cannot see. Measured: the bar is 53px. */
.top {
    position: sticky; top: 0; z-index: 10;
    border-bottom: 1px solid var(--line-soft);
    background: var(--bg);
}
[id] { scroll-margin-top: 4.5rem; }
.top .wrap {
    display: flex; align-items: center; gap: 1rem;
    flex-wrap: wrap; padding-top: .85rem; padding-bottom: .85rem;
}
.mark {
    font-family: var(--mono); font-size: .95rem; font-weight: 600;
    letter-spacing: -.02em; color: var(--text); text-decoration: none;
}
.mark::before {
    content: ""; display: inline-block; vertical-align: -1px;
    width: .62em; height: .62em; margin-right: .5em; border-radius: 2px;
    background: var(--accent);
}
.top nav { margin-left: auto; display: flex; gap: 1.1rem; flex-wrap: wrap; }
/* The one link that leaves the site, set apart from the two that do not. Four
   entries that look alike behave three ways otherwise, and the difference is
   only discovered by losing your place. */
.top nav .leave { margin-left: 1.1rem; }
.top nav a { font-size: .875rem; color: var(--dim); text-decoration: none; }
.top nav a:hover { color: var(--text); text-decoration: underline; }

/* -- the one entry that opens ---------------------------------------------
   ON HOVER, IN PLACE, MOVING NOTHING. The bar keeps its five words and its
   height whatever happens; the panel is absolute, so nothing around it is
   pushed, and it carries no caret -- a thing that appears under the pointer has
   already announced itself.

   THE BRIDGE IS NOT DECORATION. The panel hangs level with the bottom of the
   bar, which leaves .85rem of nothing between the word and it; a pointer
   crossing that gap would leave the word, close the panel, and never arrive.
   `.drop::after` is a transparent strip over exactly that gap, so the pointer
   stays inside the thing it is aiming at.

   AND IT OPENS FOR A KEYBOARD TOO, on `:focus-within`: the word is a link, so
   it takes focus on its own, and the three inside follow it in the tab order.
   Nothing here needs a pointer, and nothing here needs a script. */
.top nav .drop { position: relative; display: inline-flex; }
/* A BUTTON DRAWN AS ONE OF THE WORDS BESIDE IT. A button carries a font, a
   padding, a border and a line-height of the browser's choosing, and every one
   of them pushed this entry out of line with the four links -- which is how it
   was reported. All four are reset, and the size and ink come from the rule
   above that dresses `.top nav a`. */
.top nav .drop-word {
    position: relative; z-index: 1;
    margin: 0; padding: 0; border: 0; background: none;
    font: inherit; font-size: .875rem; line-height: inherit;
    color: var(--dim); cursor: pointer;
}
.top nav .drop-word:hover { color: var(--text); text-decoration: underline; }
.top nav .drop-word:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.drop::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: 100%; height: .95rem;
}
.drop-menu {
    position: absolute; right: 0; top: calc(100% + .85rem); z-index: 11;
    display: none; flex-direction: column; gap: .1rem;
    min-width: 12rem; padding: .4rem;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg); box-shadow: var(--shot-shadow);
}
.drop:hover .drop-menu,
.drop:focus-within .drop-menu { display: flex; }
.top nav .drop-menu a {
    display: flex; align-items: baseline; gap: .5rem;
    padding: .35rem .55rem; border-radius: 6px;
    color: var(--text); white-space: nowrap;
}
/* Where it lives, at the weight of a place and not of a name. */
.drop-where {
    margin-left: auto;
    font-size: .78em; color: var(--dim);
}
.top nav .drop-menu a:hover { background: var(--bg-soft); text-decoration: none; }
/* THREE KINDS OF THING, SO THREE GROUPS. The menu read as one list of four --
   reported -- when it is the whole project, then the two published packages,
   then the WordPress plugin. A rule between groups says so without a word. */
.drop-sep {
    display: block; height: 1px; margin: .3rem .45rem;
    background: var(--line-soft);
}


/* -- A FILE ON A DESKTOP, AS A PARTIAL SCREENSHOT -------------------------
   IT LIVED IN how-to-use-it.html, WHICH WAS THE ONLY PAGE THAT DREW IT. The
   install page draws the same file now -- same two windows, same copy button,
   same ink -- and copying a hundred lines of rules into a second page is how
   the header's CSS drifted into three versions that did not agree. One copy,
   here, and both pages read it. */

/* -- the file, as a partial screenshot of a screen ------------------------

   REVIEWED THREE TIMES ON THE LIVE SITE, and each remark moved the picture
   one step further from a diagram. A rail of folder names beside a listing
   was a diagram. A sidebar and a pane inside one dark frame was an editor
   with a sidebar. What was asked for, twice and in these words, is a mini
   partial screenshot of a SCREEN, with the folder and the file open.

   SO IT IS TWO WINDOWS, NOT TWO PANELS, and the OVERLAP is what says screen:
   the file manager behind and up to the left, the editor in front and down to
   the right, one dropping its shadow on the other, both standing on the mat
   this page already puts behind a figure -- which, behind two windows, reads
   as the desktop they were cropped out of.

   THE TWO GROUNDS ARE WHAT SAYS TWO APPLICATIONS. The manager is painted in
   the page's own tokens and follows the reader's theme, the way a file
   manager follows the desktop it runs on; the editor keeps the terminal ink,
   pinned dark in both themes, which is what every other figure on this page
   is drawn in. Light chrome against dark ink, and nobody has to be told.

   NOTHING HERE BELONGS TO AN OPERATING SYSTEM. No menu bar, no window
   buttons, no name in a title bar but the folder's and the file's own. It
   reproduces the STYLE of a window, which nobody owns, exactly as the
   terminal below it reproduces the style of a terminal.

   THE FOLDER HOLDS ONE FILE, and the listing says so rather than inventing
   neighbours for it: the tool writes annotepage.json there and nothing else,
   so the row is one row. The folder mark is in the title bar, on the folder
   whose contents are open; the file mark is on the row, selected.

   The copy button is the landing page's, shape for shape: the two-square
   icon, the tick when it lands, the chip anchored top-right INSIDE the window
   it belongs to, where every editor puts it. Its rules are repeated here
   rather than shared, because sharing them would mean editing the landing
   page, which is frozen. */
.sc { display: grid; gap: clamp(.8rem, 2.2vw, 1.15rem); }

/* THE DESKTOP IS THE MAT. Every other figure on this page gets a --bg-soft
   ring above 48rem and none below, where a phone has no room for one; behind
   two overlapping windows the same surface is the screen they sit on, so the
   figure gains a desktop and the page gains no new idea. */
@media (min-width: 48rem) {
    .sc {
        background: var(--bg-soft);
        border-radius: calc(10px * var(--ui));
        padding: clamp(.9rem, 2.2vw, 1.35rem);
    }
}

.sc-win { border-radius: calc(8px * var(--ui)); min-width: 0; overflow: hidden; }
.sc-bar {
    display: flex; align-items: center; gap: calc(.45rem * var(--ui));
    margin: 0; padding: calc(.5rem * var(--ui)) calc(.7rem * var(--ui));
    font-weight: 600;
}
/* The two marks: a folder and a sheet, drawn in the line they sit on. */
.sc-ic {
    width: calc(.95rem * var(--ui)); height: calc(.95rem * var(--ui)); flex: none;
    fill: none; stroke: currentColor; stroke-width: 1.6;
    stroke-linecap: round; stroke-linejoin: round;
}

/* -- behind: the file manager, in the page's own theme -- */
.sc-fm {
    background: var(--bg); color: var(--text);
    border: 1px solid var(--line); box-shadow: var(--shadow);
    font-family: var(--sans); font-size: calc(.72rem * var(--ui)); line-height: 1.5;
    /* The listing is one row and it is the last thing in the window: without
       this the selected row is cut by the window's own edge, which reads as a
       list that goes on rather than a folder holding one file. */
    padding-bottom: calc(.45rem * var(--ui));
}
.sc-fm .sc-bar { background: var(--bg-soft); border-bottom: 1px solid var(--line-soft); }
.sc-path {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: calc(.3rem * var(--ui)); margin: 0;
    padding: calc(.45rem * var(--ui)) calc(.7rem * var(--ui));
    color: var(--dim);
}
.sc-row {
    display: flex; align-items: center; gap: calc(.45rem * var(--ui));
    margin: 0; padding: calc(.42rem * var(--ui)) calc(.7rem * var(--ui));
}
/* SELECTED, the way a manager selects: the row is filled, not tinted. It is
   --accent under --on-accent, the one pair on this site that is guaranteed to
   answer itself in both themes. */
.sc-row.on { background: var(--accent); color: var(--on-accent); }

/* -- in front: the editor, in the terminal ink -- */
.sc-ed {
    /* Positioned, so the whole window paints above the one behind it: two
       overlapping grid items in flow would let the manager's TEXT come back
       through the editor's ground, which is a painting-order rule and not a
       bug anybody would find by reading the markup. */
    position: relative; z-index: 1;
    background: var(--t-ground); color: var(--t-text);
    border: 1px solid var(--t-border); box-shadow: var(--shot-shadow);
    font-family: var(--mono); font-size: calc(.74rem * var(--ui)); line-height: 1.55;
}
.sc-ed .sc-bar { background: var(--t-chrome); border-bottom: 1px solid var(--t-border); }
.sc-sheet { position: relative; }
.sc-body {
    margin: 0; padding: calc(.8rem * var(--ui));
    white-space: pre; overflow-x: auto;
    color: var(--t-dim); font: inherit;
}
.sc-body .site { color: var(--t-ident); }
/* A comment in the file, at the ink a comment gets. --t-dim rather than a new
   token: it is what the terminal already uses for what is not the point of the
   line, and it holds 4.97:1 on --t-ground. */
.sc-body .cm { color: var(--t-dim); }
.sc-body .val { color: var(--t-text); }

/* THE OVERLAP, AND ONLY WHERE THERE IS ROOM FOR IT. Below 62rem -- the width
   at which this page's steps stop being two columns -- the windows unstack
   and sit one above the other at full width, because two windows overlapping
   inside 320 useful pixels hide each other. */
@media (min-width: 62rem) {
    .sc-win { grid-area: 1 / 1; align-self: start; }
    .sc-fm { width: 64%; justify-self: start; }
    .sc-ed { width: 76%; justify-self: end; margin-top: calc(3.2rem * var(--ui)); }
}

/* The copy button, anchored to the sheet of the window it belongs to. */
.sc .copy {
    position: absolute; top: calc(.5rem * var(--ui)); right: calc(.5rem * var(--ui)); z-index: 1;
    display: grid; place-items: center;
    width: calc(1.6rem * var(--ui)); height: calc(1.6rem * var(--ui)); padding: 0;
    color: var(--t-dim); background: var(--t-chrome);
    border: 1px solid var(--t-border); border-radius: 7px; cursor: pointer;
    transition: color .15s ease, background-color .15s ease, border-color .15s ease;
}
.sc .copy svg {
    width: calc(.9rem * var(--ui)); height: calc(.9rem * var(--ui));
    fill: none; stroke: currentColor; stroke-width: 1.7;
    stroke-linecap: round; stroke-linejoin: round;
}
.sc .copy .i-done { display: none; }
.sc .copy:hover { color: var(--t-ident); border-color: var(--t-ident); }
.sc .copy[data-state="done"] { color: var(--t-ground); background: var(--t-ok); border-color: var(--t-ok); }
.sc .copy[data-state="done"] .i-copy { display: none; }
.sc .copy[data-state="done"] .i-done { display: block; }
.sc .copy[data-state="failed"] { color: var(--t-shimmer); border-color: var(--t-shimmer); }
.sc .copy:focus-visible { outline: 2px solid var(--t-ident); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sc .copy { transition: none; } }


/* -- WHAT HAPPENS BETWEEN TWO STEPS ---------------------------------------
   IT LIVED IN how-to-use-it.html, which was the only page with a passage
   between two steps. The install page has one too now -- somebody has to
   leave a note before there is anything to ask about -- and the rules are the
   same rules. What stays on that page is the one line tied to its own dial:
   the step after the marker comes up only when the marker is there. */

/* -- THE THING THAT HAPPENS BETWEEN TWO STEPS -----------------------------

   Not a step: a step is something the page then SHOWS, and there is nothing to
   show here -- a deploy looks like whatever your pipeline looks like, which is
   not ours to draw. But it is not nothing either, and leaving it out made the
   remark seem to unstick itself.

   So it is a marker on the page's own axis, between the two steps it sits
   between, in the position the landing page gives the way out of a chapter --
   and deliberately NOT that shape: no border, no fill, nothing that reads as
   something to press. Four words and a turning arrow.

   It is an <li> so that the list stays a list, and it does not take a number:
   counter-increment is cancelled, so the step after it carries the number it
   would have had. */
.use-steps > li.between {
    display: flex; grid-template-columns: none;
    align-items: center; justify-content: flex-start;
    counter-increment: none;
    /* ON THE STEPS' OWN RAIL, NOT ON THE PAGE'S AXIS. Centred, it sat above
       the column of pictures, 400px from anything anybody reads: somebody
       skimming runs down the left edge, finds nothing there, and files the
       line outside the sequence -- which is exactly what "it looks like a
       comment" means. Put on that rail, the arrow takes the number's place and
       the sentence takes the caption's, so it belongs to the run without
       gaining an ounce of weight: size, colour and boldness are untouched.
       The rail is the number's box (two monospace characters at .78rem, .92rem
       wide) plus the caption's own .6rem gap. The arrow spends part of it; the
       gap is what is left. Measured: text at 96.2px, the caption of the step
       below at 96.2px, at every width -- both are in fixed rem. */
    gap: calc(.92rem + .6rem - 1.15em);
    /* A STEP LARGER THAN THE SENTENCES, and it is the only thing on the page
       between the steps: at the sentences' own .95rem it read as a stray line
       of one of them. */
    color: var(--dim); font-size: 1.05rem; font-weight: 600;
}
/* AND IT IS CLOSER THAN A STEP IS, on both sides, because it belongs to the
   passage rather than to either step. Measured at 1440px: 51 above the symbol,
   51 below, and 126 from one step to the next where two ordinary steps sit 96
   apart. It reads as punctuation between them, not as a third block. */
.use-steps > li.between { margin-top: clamp(1.4rem, 3.6vw, 3.2rem); }
/* DRAWN RATHER THAN TYPED, AND DRAWN BY US.

   Typed first, as U+21BB: it came out of the body font at whatever weight that
   font gives it -- thin, small, sitting on the baseline instead of the text's
   middle. Then drawn, as a circular arrow, which was the wrong drawing: at
   17px a circular arrow is the RETRY glyph in every icon set there is, and a
   reader sees "reload", not "deploy". It is an arrow rising from a line now:
   two strokes, no ambiguity, something pushed up and out.

   AND THE COORDINATES ARE OURS, which is the whole licence question. The idea
   -- an arrow over a line -- belongs to nobody. A `d` attribute copied out of
   an icon set is that set's file, and its licence follows it: MIT and ISC ask
   for their notice to be kept in the distribution, Font Awesome Free's SVGs
   are CC BY and ask for visible attribution. Nothing is copied here, so this
   repository owes nobody a notice.

   NO ROTATION. Two figures already loop for ever on this page and they carry
   the content; a third movement in the middle of a sentence would take
   attention from them to punctuate. A straight arrow has nothing to turn. */
/* THE RULE IS ON THE MARKER'S ICON, NOT ON ONE DRAWING. It said `.ship`, which
   was fine while one page had one marker; the install page's marker says
   "somebody annotates the page" and the arrow is the DEPLOY glyph, so it draws
   something else. Two markers, two glyphs, one size and one ink. */
.use-steps > li.between svg {
    flex: none; width: 1.15em; height: 1.15em;
    color: var(--accent);
}


/* -- THE WAY OUT OF A PAGE ------------------------------------------------
   IT WAS WRITTEN TWICE, IDENTICALLY, in index.html and how-to-use-it.html --
   compared rule by rule before this move -- and the questions page wants it as
   a third. Outlined and not filled: it is the way OUT of what has just been
   read, and a filled button would compete with the thing being read.

   The margins around it stay on each page: the landing page and the usage page
   space it differently, and that is theirs to decide. */

.more {
    display: inline-flex; align-items: baseline; gap: .45rem;
    font-size: .95rem; font-weight: 600;
    color: var(--accent); background: var(--bg);
    border: 1px solid var(--accent); border-radius: 8px;
    padding: .6rem 1.3rem;
    text-decoration: none;
    transition: background .18s ease, color .18s ease;
}
.more .arrow { font-size: .9em; transition: transform .18s ease; }
.more:hover { background: var(--accent); color: var(--on-accent); }
.more:hover .arrow { transform: translateX(2px); }
.more:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
    .more .arrow { transition: none; }
}


/* THE THREE TINTS ONLY A REPLAY NEEDS, beside the terminal ink above rather
   than on the page that used to be the only one with a session. They were on
   that page; base.css referred to them from here the day the rules moved, and
   the second page then drew a sent line with no band behind it -- a custom
   property that resolves nowhere fails silently, which is why
   tools/check-css-balance.mjs now refuses one.

   Measured on rendered pixels against the #1a1a1a ground, and the figures
   cannot differ between page themes because nothing painted on that ground
   depends on one. The band behind a sent line is a fill. `subtle` is the
   prompt chevron at 2.16:1: it is the glyph the interface paints outside the
   band, measured off a real session, and it is aria-hidden -- the band, not
   the chevron, says whose line it is. */
:root {
    --t-subtle:  #505050;
    --t-userbg:  #373737;
    /* Not in the binary's table: measured off a real session instead, on the
       lossless capture rather than the lossy one. The bullet in front of a
       tool call is rgb(82,151,248) where the bullet in front of the
       assistant's own prose is plain white, and an identifier quoted inside a
       sentence is rgb(164,203,250). One bullet for everything was the clearest
       tell the figure had left. */
    --t-tool:    #5297f8;
}

/* == A SESSION REPLAYING, AND THE CHROME UNDER A FIGURE ====================

   WRITTEN ON ONE PAGE, NEEDED ON TWO. Every rule below stood in
   how-to-use-it.html and was moved here whole -- not retyped -- the day the
   install page ended on a session of its own. The menus of this site diverged
   once because a shell was copied rather than shared, and 250 lines of
   timeline chrome would have diverged the same way, only less visibly.

   The markup these rules expect, and the script that drives it, are in
   /replay.js: one root carrying `.rp`, the lines inside it carrying
   `data-step` and `data-at`, and the control in the caption carrying
   `.replay`. Nothing here knows what a session says.
   ======================================================================== */

/* The terminal is a .frame.term from base.css: same ground, same border, same
   pair of rules round the input. What it needs on top is a session's shape --
   a fixed pane that never scrolls, and a monospace size that holds the
   capture's columns. */
.frame.rp {
    display: block; padding: 0; min-height: 0;
    font-family: var(--mono); font-size: 13.5px; line-height: 1.35;
    letter-spacing: 0;
}
.rp-flow {
    padding: .9rem .8rem .4rem; min-width: 0;
    /* A terminal has no scrollbar in the middle of a recording. The pane is
       clipped and the script moves it, one jump at a time, forward only. */
    overflow: hidden;
}
/* THE SESSION FILLS THE PANE FROM THE TOP. While the pane is exactly as tall
   as the finished session, holding the lines against the bottom spends the
   first seconds with four hundred pixels of empty black above them -- which is
   the same shape as an embed that failed to load. Top down, the first line
   lands where the eye already is. */
.rp[data-play] .rp-flow {
    /* A fallback for the instant before the script runs. The height that
       survives is written by the script, which lays the finished session out
       once and measures it -- so it is right at every width, in every font
       fallback, and it cannot drift when a line above is edited. */
    height: 22rem;
    display: flex; flex-direction: column;
}
.rp[data-play] .rp-flow > * { flex: none; }
/* 100ch: the longest row in the session is 91 characters, so this clears every
   one of them and the pane, not this rule, is what a line wraps against. */
.rp-flow > * { max-width: 100ch; }

/* The user's own turn, once it has been sent. Measured off a real session
   rather than reasoned about: the chevron sits OUTSIDE the band, on the
   terminal's own ground, and the band hugs the text. The glyph is U+276F, not
   the ASCII `>`: the older builds draw `>`, the current ones draw the
   chevron. */
.rp-user {
    margin: 0 0 .4rem; color: var(--t-text);
    white-space: pre-wrap; overflow-wrap: break-word;
    padding-left: 2ch; text-indent: -2ch;
}
.rp-user .ps, .rp-line .ps { color: var(--t-subtle); }
/* AND THE GUTTER THE COPY CHIP NEEDS, where there is one. A session whose
   first line is the thing to copy carries the page's copy chip in the frame's
   top right corner, and at 500px that line wraps -- its first row ran under
   the chip and lost three words. Only this line can reach the chip: every
   other one starts lower than the chip ends. So the gutter is reserved on the
   line, not on the pane, which would indent a session nobody is copying. */
.rp:has(.copy) .rp-user { padding-right: 2.6rem; }
.rp-user .band {
    background: var(--t-userbg);
    padding: .1rem .35rem; border-radius: 2px;
    -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
/* The tool call and its result are <code> outside a <pre>. A terminal has
   neither a border nor a fill round a line. */
.rp-tool, .rp-out {
    display: block; background: none; border: 0; border-radius: 0;
    font-family: inherit; font-size: inherit;
}
/* The assistant's own words, and its tool calls: the round bullet at column
   1, the text from column 3, wrapped continuations lined up under it. */
.rp-say, .rp-tool {
    position: relative; margin: 0 0 .4rem; padding-left: 2ch;
    white-space: pre-wrap; overflow-wrap: break-word; color: var(--t-text);
}
.rp-say::before, .rp-tool::before {
    content: "\25CF"; position: absolute; left: 0; top: 0;
}
/* THE BULLET IS NOT ONE COLOUR. It is coded by what produced the line: the
   assistant's own prose takes a white bullet, a tool call takes the tool's
   colour -- blue for Bash, which is every tool call in this session. The
   tool's NAME is bold and its argument is not. */
.rp-say::before { color: var(--t-text); }
.rp-tool::before { color: var(--t-tool); }
.rp-tool b { font-weight: 700; }
/* A TOOL CALL IS ONE ROW, ALWAYS, cut off with an ellipsis rather than
   wrapped -- which is what the interface does with a command longer than the
   window. It matters more than it looks: the commit message is 91 characters
   and wrapped it ran to three rows on a phone, and six rows of broken shell is
   the most intimidating thing a figure like this can show somebody who writes
   none. */
.rp-tool { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* An identifier quoted inside a sentence is tinted where the rest of the
   sentence is not -- here the version a fix ships in, which is the only
   identifier this session's prose contains. */
.rp-say .id { color: var(--t-ident); }
/* A tool result: two spaces, the hooked prefix, two spaces, then the output
   from column 5 -- dim, because it is the machine talking and not the
   assistant. It arrives whole. Nothing here is ever typed out. */
.rp-out {
    position: relative; margin: -.3rem 0 .55rem; padding-left: 5ch;
    color: var(--t-dim);
    white-space: pre-wrap; overflow-wrap: break-word;
}
.rp-out::before { content: "\23BF"; position: absolute; left: 2ch; top: 0; }
/* THE REAL INTERFACE COLLAPSES TOOL OUTPUT BY DEFAULT: it prints the call, and
   under it one dim line reading the number of rows it is not showing. This
   figure collapses the same way, and every count is the exact number of lines
   the capture holds and this figure does not show. Two results carry one line
   out of the collapse, and each is the one line of its capture that decides
   something: the measurement, because a measurement being taken is the
   project's whole argument, and `Reported success!`, which is the moment the
   fix stops being local. */
.rp-out .trunc { color: var(--t-dim); }
.rp-out .ok { color: var(--t-ok); }
/* Between passes. The pane empties and this line holds alone for a second and
   a half, so a reader arriving in the middle of the second pass does not spend
   it reconciling two sessions that are the same session. It is the figure's
   own mark, bracketed, because no terminal prints it. */
.rp-restart { margin: 0; color: var(--t-dim); font-style: italic; display: none; }
.rp[data-play][data-restart] .rp-restart { display: block; }

/* -- the working indicator ------------------------------------------------
   The part a still frame cannot show. One blank line above it, column 1, and
   it sits under the transcript for the whole turn. It is aria-hidden: it
   carries no fact, and read aloud it would be noise between the sentences that
   do. One line of room is kept whether or not anything is in it, so the input
   box below never moves when the turn ends.

   WHAT IS NOT IN IT: the elapsed clock and the token count the interface
   prints beside the verb. Both are real and both were dropped -- a number
   climbing while nothing else moves is the figure saying "this took effort".
   What the turn cost is said once, at the end, in the past tense. */
.rp-work {
    visibility: hidden; min-height: 1.4em;
    margin: .5rem .8rem .2rem; color: var(--t-assistant);
}
.rp[data-play][data-working] .rp-work { visibility: visible; }
/* The frame cell is always two columns wide, whatever glyph is in it. That
   fixed box is what stops the verb twitching when a frame measures wider. */
.rp-frame { display: inline-block; width: 2ch; text-align: left; }
.rp-verb { white-space: pre; }
/* Three cells of brighter ink crossing the verb, one whole cell at a time. A
   gradient would be smoother and therefore wrong: the real one can only
   address whole character cells. */
.rp-verb .lit { color: var(--t-shimmer); }

/* Two full-width rules and nothing else: no verticals, no corners. Drawn as
   1px borders in base.css, never as box-drawing characters -- on a fallback
   font those stop tiling and the column drifts. */
.rp-input { margin: .2rem .8rem 0; flex: none; }
.rp-line {
    padding-left: 2ch; text-indent: -2ch;
    white-space: pre-wrap; overflow-wrap: break-word;
}
/* The chevron's gap is the literal space after it plus the negative indent,
   as it was captured. `.mt-input .ps` brings .6ch of margin for the crop on
   the other two pages, where there is no indent to answer it. */
.rp-line .ps { color: var(--t-dim); margin-right: 0; }
/* The hint row under the lower rule, and WHICH hint belongs when. A finished
   session that still offers to interrupt something is a line no session ever
   printed: while a turn runs the row reads `esc to interrupt`, and when
   nothing is running it reads `? for shortcuts`. With the script off nothing
   is running, so the idle hint is the one the static transcript carries. */
.rp-hint { margin: .1rem .8rem .55rem; color: var(--t-dim); font-size: 12px; }
.rp-hint .busy { display: none; }
.rp[data-play][data-working] .rp-hint .busy { display: inline; }
.rp[data-play][data-working] .rp-hint .idle { display: none; }
/* What a turn leaves behind when it ends. The indicator does not simply
   vanish -- it is replaced, IN PLACE, by a line saying how long the turn took,
   and that line stays in the scrollback. They used to be stacked, and the
   figure grew by a line at the exact moment it was supposed to be settling.
   The number is not chosen: the script writes the elapsed time its own
   timeline produced, floored the way the counter above it floors. */
.rp-worked {
    display: none; min-height: 1.4em;
    margin: .5rem .8rem .2rem; color: var(--t-dim);
}
/* U+273B, and it is one of the twelve the indicator actually draws -- which
   matters, because this line REPLACES that indicator in the same cell. Its
   predecessor was in no frame of it, and at 12px on a phone it painted nothing
   at all. */
.rp-worked .g { display: inline-block; width: 2ch; color: var(--t-assistant); }
.rp[data-play][data-worked] .rp-worked { display: block; }
.rp[data-play][data-worked] .rp-work { display: none; }
/* The block cursor is not a separate box that could push the line about: it is
   the NEXT cell, painted. It stays solid while characters are arriving -- a
   caret that blinks through its own typing is the tell. */
.rp[data-play] .rp-line .ch { visibility: hidden; }
.rp[data-play] .rp-line .ch.on { visibility: visible; }
.rp[data-play] .rp-line .ch.cur {
    visibility: visible; background: var(--t-text); color: transparent;
    display: inline-block; width: 1ch;
}
.rp[data-play][data-idle] .rp-line .ch.cur { animation: rp-blink 1s steps(1, end) infinite; }
@keyframes rp-blink { 50% { opacity: 0; } }
/* Nothing appears until the script says so, and only then. */
.rp[data-play] [data-step] { display: none; }
.rp[data-play] [data-step].on { display: block; }

@media (max-width: 700px) {
    .frame.rp { font-size: 12px; }
    .rp[data-play] .rp-flow { height: 26rem; }
}

/* -- under a figure ------------------------------------------------------- */

.fig { margin: 0; }
/* NOTHING IS WRITTEN UNDER A FIGURE ANY MORE -- the pictures say it, and a
   line repeating them scored zero. What is left is the button, and the button
   is itself hidden unless the reader has asked for no motion or the figure has
   run out. So the strip carries NO margin of its own: an empty caption that
   still pushed the next step down would be spacing for nothing. The margin
   arrives with the button, in the same rule that reveals it. */
.fig-foot {
    display: flex; align-items: baseline; gap: .8rem 1.2rem; flex-wrap: wrap;
}
/* THE LOOP HAS TO LOOK LIKE A LOOP. Both figures run for ever now -- they are
   the content of this page, not decoration beside it -- and a restart that
   simply swaps the screen reads as a glitch: somebody looking away for a second
   cannot tell whether they missed something or whether the figure moved on its
   own. So the frame dims while it rewinds, and comes back up half a second
   before it starts again: you see it RETURN, then go.

   Opacity and not display: the box keeps its size, so nothing below it jumps.
   And it is a transition rather than an animation, so the reduced-motion block
   further down can stop it with the rest -- there, neither timeline arms at
   all, and a figure that never rewinds never dims. */
/* Named on the class and not on an id, because two pages carry a replay now
   and an id cannot be in two places. The usage page's gesture figure keeps its
   own pair beside this one: it rewinds the same way and is not a session. */
.rp { transition: opacity .52s ease; }
.rp[data-rewind] { opacity: .16; }

/* AND THE RESTART IS SAID, NOT ONLY SHOWN. Reviewed on the live site: the
   dim-and-return reads as a restart once you already know the figure loops,
   and not before. So a badge appears in the middle of the frame for exactly as
   long as the frame is dimmed, and disappears with it.

   IT IS OUTSIDE THE FRAME IN THE MARKUP, and it has to be: opacity applies to
   a whole subtree, so a badge inside the frame would fade to .16 along with
   everything it is there to explain.

   The circular arrow is the retry glyph of every icon set there is -- the
   wrong drawing for a deploy, which is why the marker between two steps uses
   an arrow out of a line, and exactly the right one here. Same two paths,
   drawn by us, owed to nobody.

   Nothing appears for a reader who asked for no motion: neither figure ever
   rewinds there, so neither ever carries the attribute this hangs off. */
.fig { position: relative; }
.restart {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem 1.05rem .55rem .8rem;
    border-radius: 999px;
    background: var(--bg); border: 1px solid var(--line);
    box-shadow: var(--shadow);
    color: var(--dim); font: 600 .95rem/1 var(--sans);
    opacity: 0; transition: opacity .3s ease;
    pointer-events: none; white-space: nowrap;
}
.restart svg { flex: none; width: 1.25rem; height: 1.25rem; color: var(--accent); }
.fig > [data-rewind] ~ .restart { opacity: 1; }

/* IT HAS TO BE READ, NOT GLIMPSED. Reviewed on the live site: a badge that
   appears and goes inside a second is not understood, it is only noticed. So
   three things, and the three of them are the answer to that one remark: the
   gap between two passes goes from 1.6s to 2.6s, the badge SAYS the word
   instead of leaving a glyph to carry it alone, and the arrow turns once --
   one turn, at the speed of the pause, not a spinner. A spinner would say
   "wait"; this says "it is going round again", which is the truth.

   The turn is an animation, so the page's own reduced-motion rule stops it
   with everything else -- and there, no figure ever rewinds, so nothing of
   this is ever on screen anyway. */
@keyframes ap-restart-turn {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
.fig > [data-rewind] ~ .restart svg { animation: ap-restart-turn 1.6s ease-in-out; }

.replay {
    display: none; flex: none;
    font: 600 .85rem/1 var(--sans); color: var(--text);
    background: var(--bg-soft); border: 1px solid var(--control-line);
    border-radius: 999px; padding: .5rem .95rem; cursor: pointer;
}
.replay:hover { border-color: var(--accent); color: var(--accent); }
/* Shown when the figure has finished, and shown at once when the reader has
   asked for no motion -- in that case nothing plays unless they press it. */
[data-done] ~ .fig-foot .replay,
[data-quiet] ~ .fig-foot .replay { display: inline-block; }
/* THE COPY BUTTON IS NOT A REPLAY: it is always there, because what it copies
   is always there. Same shape, so the strip reads as one kind of thing.
   It lives in the caption and NOT in the frame, because the frame is
   aria-hidden -- a control inside it would be a control nobody using a screen
   reader could reach. */
.fig-foot .copy {
    display: inline-block; flex: none;
    font: 600 .85rem/1 var(--sans); color: var(--text);
    background: var(--bg-soft); border: 1px solid var(--control-line);
    border-radius: 999px; padding: .5rem .95rem; cursor: pointer;
}
.fig-foot .copy:hover { border-color: var(--accent); color: var(--accent); }
.fig-foot .copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fig-foot:has(.copy) { margin-top: .95rem; }
@media (min-width: 48rem) {
    .fig-foot:has(.copy) { margin-top: 1.7rem; }
}
[data-done] ~ .fig-foot,
[data-quiet] ~ .fig-foot { margin-top: .95rem; }
@media (min-width: 48rem) {
    /* Clear of the mat the frame paints around itself. */
    [data-done] ~ .fig-foot,
    [data-quiet] ~ .fig-foot { margin-top: 1.7rem; }
}
