/* leaf.css — the notebook itself: palette, page, ruling, the turn, the
   fore-edge. Shared by every app that renders one of these notebooks, so they
   are the same object and not two things that resemble each other. Each app
   styles its own chrome on top. */
:root {
    --desk:#e4ddcd;
    --paper:#f6f2e9; --paper2:#efe9dc; --leafpaper:#f8f5ee; --beyond:#e9e2d2;
    --ink:#23201a; --ink2:#5d564a; --ink3:#9a9080;
    --rule:rgba(35,32,26,.16); --line:rgba(70,100,140,.20); --margin:#b9705f;
    --accent:#3f6b4f; --ribbon:#9d3b2f; --gold:#b5730a;
    --edge:35,32,26;                       /* fore-edge marks, as an rgb triple */
    --nib-pen:#23201a; --nib-pencil:#4a443a; --nib-marker:#b5730a;
    /* We handle both schemes ourselves. Declaring only `light` — even `only
       light` — does not reliably stop Chrome's Auto Dark Theme on Android,
       because that feature exists precisely to darken sites that have no dark
       mode. Having a real one is the opt-out that actually holds. */
    color-scheme: light dark;
  }

  /* Night. Not an inversion of the paper — inverted paper is a grey smear.
     A dark notebook is its own object: near-black board, warm off-white
     writing, the ruling dropped back so it reads as impression rather than
     print. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="paper"]) {
      --desk:#100e0c;
      --paper:#1c1916; --paper2:#242019; --leafpaper:#1b1815; --beyond:#15130f;
      --ink:#ece6d8; --ink2:#b3aa99; --ink3:#7d7565;
      --rule:rgba(236,230,216,.17); --line:rgba(122,152,190,.20); --margin:#a8574a;
      --accent:#84b391; --ribbon:#c4564a; --gold:#d79a2b;
      --edge:236,230,216;
      --nib-pen:#ece6d8; --nib-pencil:#b0a795; --nib-marker:#d79a2b;
      color-scheme: dark;
    }
  }
  :root[data-theme="night"] {
    --desk:#100e0c;
    --paper:#1c1916; --paper2:#242019; --leafpaper:#1b1815; --beyond:#15130f;
    --ink:#ece6d8; --ink2:#b3aa99; --ink3:#7d7565;
    --rule:rgba(236,230,216,.17); --line:rgba(122,152,190,.20); --margin:#a8574a;
    --accent:#84b391; --ribbon:#c4564a; --gold:#d79a2b;
    --edge:236,230,216;
    --nib-pen:#ece6d8; --nib-pencil:#b0a795; --nib-marker:#d79a2b;
    color-scheme: dark;
  }
  :root[data-theme="paper"] { color-scheme: light; }

  *{box-sizing:border-box;margin:0;padding:0}
  html,body{height:100%;overflow:hidden;overscroll-behavior:none}
  body{background:var(--desk);color:var(--ink);
    font:14px/1.55 ui-serif,Georgia,"Times New Roman",serif;
    -webkit-font-smoothing:antialiased;height:100dvh;
    display:flex;flex-direction:column;
    padding:0 0 env(safe-area-inset-bottom);
    touch-action:none;-webkit-user-select:none;user-select:none}

  /* ---- chrome ---- */
  header{display:flex;align-items:center;gap:10px;padding:5px 12px;
    border-bottom:1px solid var(--rule);background:var(--paper);flex:none;
    padding-top:calc(5px + env(safe-area-inset-top))}
  h1{font-size:11px;letter-spacing:.24em;text-transform:uppercase;font-weight:600;
    color:var(--accent);font-family:ui-sans-serif,system-ui,sans-serif}
  .sub{font-size:11px;color:var(--ink3);font-style:italic;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}
  /* The tool strip wraps rather than overflowing; on a phone there are more
     controls than there is width, and a row that scrolls off-screen hides
     half the app. */
  .strip{flex:none;display:flex;flex-wrap:wrap;justify-content:center;gap:4px;
    align-items:center;padding:7px 10px calc(7px + env(safe-area-inset-bottom));
    border-top:1px solid var(--rule);background:var(--paper)}
  .strip button,.mbtn{border:1px solid var(--rule);background:var(--paper2);
    font:inherit;font-size:11px;padding:5px 8px;border-radius:3px;cursor:pointer;
    color:var(--ink2);font-family:ui-sans-serif,system-ui,sans-serif;
    min-height:32px;min-width:32px;line-height:1;white-space:nowrap}
  /* Wide: the secondary buttons dissolve into the strip's own flex flow. */
  .sec{display:contents}
  #more{display:none}
  .strip button[aria-pressed=true]{background:var(--ink);color:var(--paper);border-color:var(--ink)}
  .strip button:disabled{opacity:.35;cursor:default}
  .sep{width:1px;height:20px;background:var(--rule);margin:0 3px;flex:none}

  /* ---- book ---- */
  .book{flex:1;min-height:0;display:flex;align-items:stretch;position:relative;
    padding:12px 18px 12px 12px;gap:10px}
  .stage{flex:1;min-width:0;position:relative;overflow:hidden;
    display:flex;align-items:center;justify-content:center;
    perspective:2200px;perspective-origin:50% 45%;
    /* How far open the book is, 0 closed and 1 open. A closed book has one
       board to show and it belongs in the middle of the screen; an open one
       puts its gutter there instead, so the spine sits half a page further
       right. EVERY layer of the book has to share that offset — both slots,
       the turning leaf and its cast shadow — or the turn that takes you from
       one to the other is drawn in one coordinate system while the pages are
       still placed in the other, and the cover lands on top of the page it is
       supposed to be revealing. The turn animates `--open`, so the book
       spreads open as the cover swings instead of teleporting under it. */
    --open:1;
    /* Where the room for a stack has to be taken from the page rather than
       found spare, it is taken from ONE side — so the book itself has to sit
       off-centre by half of it, or the room opens up symmetrically and the
       stack is clipped by the stage on the side it was meant to stand. */
    --bias:0px; --biasy:0px;
    --shift:calc((1 - var(--open)) * var(--sw) / -2 + var(--bias))}

  .slot{position:absolute;top:50%;left:50%;transform-origin:center;
    width:var(--sw);height:var(--sh);
    margin:calc(var(--sh) / -2 + var(--biasy)) 0 0 calc(var(--sw) / -2 + var(--bias))}
  .stage[data-mode=spread] .slot.L{margin-left:calc(var(--shift) - var(--sw))}
  .stage[data-mode=spread] .slot.R{margin-left:var(--shift)}
  .stage[data-mode=spread] .slot.L .page{border-radius:4px 0 0 4px}
  .stage[data-mode=spread] .slot.R .page{border-radius:0 4px 4px 0}
  /* The gutter: two pages meeting flat with no shadow read as one wide sheet
     rather than an open book. */
  .stage[data-mode=spread] .slot.L .page::after,
  .stage[data-mode=spread] .slot.R .page::after{content:'';position:absolute;
    top:0;bottom:0;width:26px;pointer-events:none;z-index:3}
  .stage[data-mode=spread] .slot.L .page::after{right:0;
    background:linear-gradient(to right,transparent,rgba(35,32,26,.13))}
  .stage[data-mode=spread] .slot.R .page::after{left:0;
    background:linear-gradient(to left,transparent,rgba(35,32,26,.13))}
  .stage[data-mode=single] .slot.L{display:none}

  /* ---- the stack ---- */
  /* A book tells you where you are with its own body: the leaves you have
     turned pile up on one side, the ones left thin out on the other, and near
     the middle the thing lies flat. `--pread` is how far through you are, 0..1,
     and the two stacks share a fixed thickness because the number of leaves is
     fixed — a book cannot get thicker by being read.

     This is SCENERY. Its whole information content is one number the fore-edge
     already shows at forty times the resolution, so it is worth having where
     it is free and not worth much where it is not — which is why `--tmax` is
     computed in layout() out of whatever space is left over, and why a phone
     gets one stack rather than two. See STACK_CAP in journal.js.

     The page does NOT slide as the stack grows. That is not realism
     sacrificed: a real open book has its gutter fixed and its OUTER edges
     moving, which is exactly this. Pinning the page and pinning the spine are
     the same act here. */
  .stage{--pread:0;--tmax:0px}
  .stack[hidden]{display:none}
  /* A book shown closed is shown face-on, and face-on you do not see its
     fore-edge: the pages are behind the cover. Showing the block of leaves
     beside the cover put the thickness where no thickness is visible and
     made the first thing you see the busiest. They come back as the cover
     opens — `data-closed` is cleared when the turn STARTS, not when it
     lands, so they fade in with it rather than appearing afterwards. */
  .stack{transition:opacity .28s ease}
  .stage[data-closed] .stack{opacity:0;pointer-events:none}
  @media (prefers-reduced-motion: reduce){ .stack{transition:none} }
  /* The stacks are scenery for a finger and a control for a cursor: the book
     is 11.6px thick on a phone, which no thumb can aim into, and 56px on a
     laptop, which a mouse can. `pointer-events` follows that exactly. */
  .stack{position:absolute;pointer-events:none;z-index:0;
    /* The leaves themselves are PAINTED — see stack.js. Tiling them with a
       repeating gradient makes a comb, and a comb reads as corduroy however
       fine the pitch, because every line is the same weight at exactly the
       same spacing, which is the one thing a stack of paper never is.
       A stack also does not end square, so the last few pixels at head and
       tail fade: the cheapest honest version of leaves curving over. */
    --taper:linear-gradient(to bottom,transparent 0,#000 7px,
      #000 calc(100% - 7px),transparent 100%);
    top:50%;height:calc(var(--sh) - 5px);
    margin-top:calc((var(--sh) - 5px) / -2 + var(--biasy));
    -webkit-mask-image:var(--taper); mask-image:var(--taper);
    filter:drop-shadow(0 2px 4px rgba(20,12,8,.34))}
  .stack canvas{display:block;width:100%;height:100%}
  /* Declared AFTER the base rule, or it loses on order and the stacks stay
     untouchable — which is how the first attempt silently did nothing. */
  @media (pointer: fine){
    .stack{pointer-events:auto;cursor:ew-resize}
  }
  /* Half the book's own width, which interpolates correctly through the
     cover-opening turn: half a page when one leaf is shown or the book is
     shut, a whole page when it is open. Deriving it from `--shift` instead
     put the left-hand stack a full page-width away from a closed cover. */
  .stage{--half:calc(var(--sw) / 2)}
  .stage[data-mode=spread]{--half:calc(var(--sw) * (1 + var(--open)) / 2)}
  .stack.L{left:calc(50% - var(--half) + var(--bias));transform:translateX(-100%);
    width:calc(var(--tmax) * var(--pread));border-radius:3px 0 0 3px}
  .stack.R{left:calc(50% + var(--half) + var(--bias));
    width:calc(var(--tmax) * (1 - var(--pread)));border-radius:0 3px 3px 0}
  .stage[data-stacks=L] .stack.R,
  .stage[data-stacks=R] .stack.L{display:none}
  /* A top-bound pad does NOT pile leaves above its head: the turned sheets
     fold back over the wire and hang down the back, so face-on the pad simply
     thins from the foot. Drawing a stack above the head would also put paper
     on top of the binding, since the rings straddle that exact band. */
  .stage[data-binding=top] .stack{
    top:auto;bottom:auto;margin-top:0;height:auto;transform:none;
    left:50%;width:calc(var(--sw) - 5px);
    margin-left:calc((var(--sw) - 5px) / -2 + var(--bias));
    --taper:linear-gradient(to right,transparent 0,#000 7px,
      #000 calc(100% - 7px),transparent 100%)}
  .stage[data-binding=top] .stack.R{top:calc(50% + var(--biasy) + var(--sh) / 2);
    height:calc(var(--tmax) * (1 - var(--pread)));border-radius:0 0 3px 3px}

  /* ---- the riffle ---- */
  /* Going twenty leaves at once is not twenty turns; it is ONE turn of a
     wedge. Jumping used to swap the page with no motion at all, which is why
     a jump from the fore-edge felt like the list it is drawn from rather than
     like a book falling open. These leaves carry no writing on purpose —
     nothing is readable at riffle speed, and dressing seven pages per jump
     would cost more than the whole animation is worth. */
  /* The fanned leaves are drawn in 3D and perspective will always throw their
     far corners wide; the book is what they belong inside. */
  .fan{position:absolute;top:50%;left:50%;width:var(--sw);height:var(--sh);
    margin:calc(var(--sh) / -2 + var(--biasy)) 0 0 calc(var(--sw) / -2 + var(--bias));
    transform-style:preserve-3d;pointer-events:none;
    opacity:0;visibility:hidden;z-index:6}
  .stage.fanning .fan{opacity:1;visibility:visible}
  /* The turning block rides above the page it is uncovering, and above the
     stacks it came out of. */
  .stage.fanning .cast{z-index:7}
  /* The block's own shading. #shade is no use here: it lives inside
     .leafwrap, which stays opacity:0 until .flipping, so every bulk turn was
     flat evenly-lit paper. Each leaf darkens itself instead, which also means
     the shading survives the 180-degree point where the leaf shows its back. */
  .fan i::after,.fan .fanback::after{content:'';position:absolute;inset:0;
    border-radius:inherit;background:#231f19;
    opacity:var(--fanshade,0);pointer-events:none;z-index:2}
  /* The underside carries writing, and it is the page you are arriving at:
     it takes the block's shading at half strength so it stays readable while
     it comes down. */
  .fan .fanback::after{opacity:calc(var(--fanshade,0) * .45)}
  /* The UNDERSIDE of the block. A block does not land face-first: the leaf
     that ends up on top of the read pile is the one at the BOTTOM of what you
     picked up, so it is the LAST to come down. With one dressed leaf leading
     the block, the destination page landed first and the blank leaves piled
     on top of it — which is why it read as one page turning with a stack
     bolted on rather than a stack with the page under it. */
  .fan .fanback{position:absolute;inset:0;border-radius:4px;
    transform-origin:left center;transform-style:preserve-3d;
    background:var(--leafpaper)}
  .fan .fanback .face{position:absolute;inset:0;backface-visibility:hidden}
  .fan .fanback .face.back{transform:rotateY(180deg)}
  .stage[data-binding=top] .fan .fanback{transform-origin:center top}
  .stage[data-mode=spread][data-dir=back] .fan .fanback{transform-origin:right center}
  /* Past edge-on you are looking at the block from underneath, so the leaves
     nearest you are the deep ones — the whole block paints above the leaf
     that led it in. */
  .stage.fanning .fan.past-half{z-index:8}
  .stage[data-mode=single] .fan .fanback .face.back{display:none}
  .stage[data-mode=spread] .fan{margin-left:var(--shift)}
  /* The backs are NOT hidden: a spread turn goes the whole 180 degrees, and
     what you should see past halfway is the blank reverse of the paper, not
     the leaf vanishing. */
  .fan i{position:absolute;inset:0;border-radius:4px;background:var(--leafpaper);
    transform-origin:left center;
    box-shadow:0 1px 2px rgba(35,32,26,.20),0 8px 22px rgba(35,32,26,.18);
    background-image:linear-gradient(to right,
      rgba(35,32,26,.16),rgba(35,32,26,0) 16%)}
  .stage[data-binding=top] .fan i{transform-origin:center top;
    background-image:linear-gradient(to bottom,
      rgba(35,32,26,.16),rgba(35,32,26,0) 16%)}
  /* A SPREAD's back-turn pivots on the other edge and starts from the other
     half — the same two rules the turning leaf already follows. A SINGLE-page
     turn does not: both of its directions pivot on the same edge, forward
     lifting the leaf off it and back bringing the leaf down onto it, so the
     origin must NOT flip there. Flipping it for both was why a bulk turn to
     the left came out backwards. */
  .stage[data-mode=spread][data-dir=back] .fan{margin-left:calc(var(--shift) - var(--sw))}
  .stage[data-mode=spread][data-dir=back] .fan i{transform-origin:right center;
    background-image:linear-gradient(to left,
      rgba(35,32,26,.16),rgba(35,32,26,0) 16%)}

  .leafwrap{position:absolute;top:50%;left:50%;width:var(--sw);height:var(--sh);
    margin:calc(var(--sh) / -2 + var(--biasy)) 0 0 calc(var(--sw) / -2 + var(--bias));
    transform-style:preserve-3d;will-change:transform;pointer-events:none;
    opacity:0;visibility:hidden}
  .stage.flipping .leafwrap{opacity:1;visibility:visible}
  /* The top leaf of a turning BLOCK is this same dressed leaf, so a bulk turn
     shows what you lifted and what is under it. z-index puts it in front of
     the blank leaves it is riding on. */
  .stage.fanning .leafwrap{opacity:1;visibility:visible;z-index:7}
  .stage[data-mode=spread] .leafwrap{margin-left:var(--shift)}
  .stage[data-mode=spread][data-dir=back] .leafwrap{margin-left:calc(var(--shift) - var(--sw))}
  .leafwrap .face{position:absolute;inset:0;backface-visibility:hidden}
  .leafwrap .face.back{transform:rotateY(180deg)}
  /* Nothing is printed on the reverse of a single-page turn, but ink does
     show through paper, and that faint mirrored ghost is most of why a real
     page looks like a real page as it lifts. */
  /* No show-through ghost in single mode. It was drawn with a `filter`, and a
     filter flattens the element out of the 3D rendering context — which
     disables backface culling, so the ghost painted ON TOP of the front face
     and every page came out doubled mid-turn. A single-page turn never rotates
     past 90 degrees, so the reverse of the leaf is never legitimately seen
     anyway: the honest fix is not to draw it. */
  .stage[data-mode=single] .leafwrap .face.back{display:none}

  /* The leaf darkens towards its own pivot. Both directions of a single-page
     turn now pivot on the SAME edge — forward lifts this page off it, back
     brings the previous page down onto it — so the gradient no longer flips
     with direction. Only a spread's back-turn pivots on the other side. */
  .shade{position:absolute;inset:0;opacity:0;pointer-events:none;border-radius:4px;
    background:linear-gradient(to right,rgba(35,32,26,.55),rgba(35,32,26,.06) 32%,transparent 62%)}
  .stage[data-binding=top] .shade{
    background:linear-gradient(to bottom,rgba(35,32,26,.55),rgba(35,32,26,.06) 30%,transparent 58%)}
  .stage[data-mode=spread][data-dir=back] .shade{
    background:linear-gradient(to left,rgba(35,32,26,.55),rgba(35,32,26,.06) 32%,transparent 62%)}
  .cast{position:absolute;top:50%;left:50%;width:var(--sw);height:var(--sh);
    margin:calc(var(--sh) / -2 + var(--biasy)) 0 0 calc(var(--sw) / -2 + var(--bias));
    opacity:0;pointer-events:none;
    transform-origin:left center;border-radius:4px;
    background:linear-gradient(to right,rgba(35,32,26,.45),rgba(35,32,26,.10) 40%,transparent 70%)}
  .stage[data-mode=spread] .cast{margin-left:var(--shift)}
  .stage[data-binding=top] .cast{transform-origin:center top;
    background:linear-gradient(to bottom,rgba(35,32,26,.45),rgba(35,32,26,.10) 38%,transparent 66%)}
  .stage[data-mode=spread][data-dir=back] .cast{transform-origin:right center;
    background:linear-gradient(to left,rgba(35,32,26,.45),rgba(35,32,26,.10) 40%,transparent 70%)}

  /* ---- the page itself ---- */
  /* Everything printed on a page lives inside .sheet, which is sized in
     LOGICAL UNITS and scaled as a whole to fit the screen. Nothing inside it
     is ever measured in screen pixels, so the same page has the same layout,
     the same ruling and the same line breaks on a phone and on a laptop —
     which is the only way absolutely-positioned ink can mean anything. */
  .page{position:absolute;inset:0;background:var(--leafpaper);border-radius:4px;
    box-shadow:0 1px 2px rgba(35,32,26,.14),0 6px 18px rgba(35,32,26,.10);
    overflow:hidden}
  .slot .page{box-shadow:0 1px 2px rgba(35,32,26,.12),0 10px 26px rgba(35,32,26,.13)}
  .page.beyond{background:var(--beyond);box-shadow:inset 0 0 0 1px var(--rule)}
  .page.beyond .sheet{visibility:hidden}

  .sheet{position:absolute;left:0;top:0;width:var(--pw);height:var(--ph);
    transform-origin:0 0;transform:scale(var(--s))}

  /* Everything inside .sheet is drawn in LOGICAL units and then scaled, so a
     line specified as 1 unit is 1 x scale pixels on screen — and a 1-unit
     margin rule on a 720-unit page shown at 0.7 came out 0.7 DEVICE pixels on
     a 1x display and rasterised to nothing at all. It survived on a 3x phone
     (1.4 device px) and vanished on a laptop, which is exactly the "the left
     margin disappeared" report, and why it seemed to come and go: sub-pixel
     phase decides it, and the phase differs per slot and per scale.
     `--hair` is a hairline that never falls below one device pixel. Where the
     line is already thicker than that it is left alone, so nothing that looks
     right today changes. DPR comes from media queries rather than from JS, so
     this file stays self-sufficient for every app that includes it. */
  :root{--dpr:1}
  @media (min-resolution:1.5dppx){:root{--dpr:2}}
  @media (min-resolution:2.5dppx){:root{--dpr:3}}
  .sheet{--hair:max(1px, calc(1px / (var(--s, 1) * var(--dpr))))}

  .rule{position:absolute;inset:0;pointer-events:none;opacity:.55;
    background:
      linear-gradient(to right,transparent calc(var(--pm) - var(--hair)),
        var(--margin) calc(var(--pm) - var(--hair)),var(--margin) var(--pm),
        transparent var(--pm)),
      repeating-linear-gradient(to bottom,transparent 0,
        transparent calc(var(--prule) - var(--hair)),
        var(--line) calc(var(--prule) - var(--hair)),var(--line) var(--prule));
    background-position:0 0,0 calc(var(--phead) + var(--pm));
    -webkit-mask-image:linear-gradient(to bottom,transparent 0,
      transparent calc(var(--phead) + var(--pm) * .35),
      #000 calc(var(--phead) + var(--pm)),
      #000 calc(100% - var(--pm)),transparent 100%);
    mask-image:linear-gradient(to bottom,transparent 0,
      transparent calc(var(--phead) + var(--pm) * .35),
      #000 calc(var(--phead) + var(--pm)),
      #000 calc(100% - var(--pm)),transparent 100%)}
  .page[data-kind=index] .rule{opacity:.28}

  .blocks{position:absolute;inset:0;font-size:var(--ptext);line-height:var(--prule)}
  .blk{position:absolute;white-space:pre-wrap;word-break:break-word;
    outline:none;color:var(--ink);overflow:hidden}
  .blk[contenteditable=true]{cursor:text;-webkit-user-select:text;user-select:text}
  /* A continuation carries a mark in the margin, so you can tell at a glance
     that this paragraph started on the page behind you. */
  .blk.cont::before{content:'';position:absolute;left:-14px;top:.15em;width:5px;
    height:1em;background:var(--ink3);opacity:.5;border-radius:2px}
  .page.live .blk.spills{box-shadow:0 2px 0 -1px var(--gold)}

  canvas.ink{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
  canvas.ink.wet{display:none}

  .folio{position:absolute;bottom:calc(var(--pm) * .40);
    font:600 15px ui-sans-serif,system-ui,sans-serif;color:var(--ink3);
    letter-spacing:.08em}
  .slot.L .folio,.leafwrap .face.back .folio{left:calc(var(--pm) * .55)}
  .slot.R .folio,.leafwrap .face.front .folio{right:calc(var(--pm) * .55)}
  .stage[data-mode=single] .folio{right:calc(var(--pm) * .55);left:auto}

  .ptitle{position:absolute;left:calc(var(--pm) * 1.12);right:calc(var(--pm) * .6);
    top:calc(var(--phead) + var(--pm) * .30);
    font:600 16px ui-sans-serif,system-ui,sans-serif;letter-spacing:.14em;
    text-transform:uppercase;color:var(--accent);outline:none;min-height:1.2em}
  .ptitle[data-empty="1"]:not(:focus)::after{content:'untitled';opacity:.32;
    font-style:italic;text-transform:none;letter-spacing:.04em}
  .ptitle[contenteditable=true]{-webkit-user-select:text;user-select:text;cursor:text}

  /* ---- fore-edge ---- */
  .foreedge{flex:none;width:30px;position:relative;cursor:ns-resize;
    touch-action:none;align-self:stretch;
    background:linear-gradient(to right,rgba(var(--edge),.03),rgba(var(--edge),.10));
    border-left:1px solid var(--rule);border-right:1px solid var(--rule)}
  .foreedge canvas{position:absolute;inset:0;width:100%;height:100%}
  .foreedge.scrubbing{background:linear-gradient(to right,rgba(var(--edge),.09),rgba(var(--edge),.20))}
  /* The edge splays under a thumb, so an exploratory touch teaches the
     gesture instead of just doing nothing visible. */
  .foreedge{transition:width .12s ease,transform .12s ease}
  .foreedge.scrubbing{width:42px}
  .canvas-wet{pointer-events:none}

  /* The text on an ink-coloured chip is `--paper`, NOT `--desk`. `--desk` is
     the surface the book lies on, and an app is entitled to make that dark —
     the journal makes it dark leather in every theme. Using it as a foreground
     against `--ink` therefore produced near-black on near-black, unreadable,
     in exactly the theme (paper) where the rest of the page looked fine.
     `--paper` is the colour DEFINED as the one ink is legible on. */
  .hud{position:absolute;right:58px;top:50%;transform:translateY(-50%);
    background:var(--ink);color:var(--paper);border-radius:4px;
    padding:8px 12px;font:12px ui-sans-serif,system-ui,sans-serif;
    pointer-events:none;opacity:0;transition:opacity .12s;max-width:190px;z-index:6}
  .hud.on{opacity:1}
  .hud b{display:block;font-size:19px;font-weight:600;letter-spacing:.02em}
  .hud i{font-style:normal;opacity:.7;display:block;font-size:11px;margin-top:2px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .hud u{display:block;text-decoration:none;font-size:10px;opacity:.55;margin-top:4px;
    letter-spacing:.04em}

  .hint{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);
    font-size:11px;color:var(--ink3);font-style:italic;pointer-events:none;
    background:var(--paper);opacity:.9;padding:3px 9px;border-radius:3px;
    transition:opacity .4s;white-space:nowrap;z-index:5}
  .hint.gone{opacity:0}


  /* The page-full signal: the ruling fades under the last lines and a mark
     appears, well before you run out. It has to arrive early and softly —
     this is the thing you hit every few minutes. */
  .blk.filling::after{content:'continues \2192';position:absolute;right:0;bottom:-1.15em;
    font:600 11px ui-sans-serif,system-ui,sans-serif;letter-spacing:.09em;
    color:var(--gold);opacity:calc((var(--full) - .82) * 5.5)}
  .blk.filling{-webkit-mask-image:linear-gradient(to bottom,#000 78%,rgba(0,0,0,.45) 97%);
    mask-image:linear-gradient(to bottom,#000 78%,rgba(0,0,0,.45) 97%)}
  .blk.filling[contenteditable=true]{-webkit-mask-image:none;mask-image:none}

  .backchip{flex:none;
    border:1px solid var(--rule);background:var(--paper);cursor:pointer;
    font:11px ui-sans-serif,system-ui,sans-serif;color:var(--ink2);
    padding:6px 10px;border-radius:3px;min-height:30px}
  .backchip:hover{border-color:var(--accent);color:var(--accent)}
  #touchmode.muted{opacity:.45;font-style:italic}

  /* The `*{margin:0}` reset above also wipes a dialog's own `margin:auto`,
     which is what centres it in the viewport — so every dialog here was
     anchored to the top-left corner and looked like a rendering fault. */
  dialog{margin:auto}
  dialog#readerdlg{border:none;border-radius:5px;padding:0;width:min(680px,92vw);
    max-height:86vh;background:var(--paper);color:var(--ink);
    box-shadow:0 20px 60px rgba(35,32,26,.4)}
  dialog#readerdlg::backdrop{background:rgba(35,32,26,.45)}
  .readerbar{display:flex;align-items:center;gap:14px;padding:10px 14px;
    border-bottom:1px solid var(--rule);font:12px ui-sans-serif,system-ui,sans-serif;
    position:sticky;top:0;background:var(--paper);z-index:2}
  .readerbar label{display:flex;align-items:center;gap:7px;color:var(--ink2);flex:1}
  .readerbar input[type=range]{flex:1;accent-color:var(--accent);max-width:220px}
  /* Chrome may scroll. The rule is that the NOTEBOOK does not scroll; making
     a reflowed accessibility view paginate would be the costume, not the app. */
  .readerbody{padding:20px 26px;overflow:auto;max-height:62vh;font-size:19px;line-height:1.62}
  .readerbody h2{font:600 12px ui-sans-serif,system-ui,sans-serif;letter-spacing:.16em;
    text-transform:uppercase;color:var(--accent);margin-bottom:12px}
  .readerbody p{margin-bottom:.85em;white-space:pre-wrap}
  .readerbody p[data-cont]::before{content:'… ';color:var(--ink3)}
  .readerbody p.muted{color:var(--ink3);font-style:italic}
  .readerink{display:block;width:100%;height:auto;margin-top:14px;
    border:1px solid var(--rule);border-radius:3px;background:var(--leafpaper)}
  .readernote{padding:0 26px 18px;font-size:11px;color:var(--ink3);font-style:italic}


  .bookwarn{flex:none;font:600 10.5px ui-sans-serif,system-ui,sans-serif;
    letter-spacing:.07em;text-transform:uppercase;color:var(--gold);
    border:1px solid rgba(181,115,10,.4);border-radius:3px;padding:3px 7px;white-space:nowrap}

  .flash{position:fixed;left:50%;bottom:78px;transform:translate(-50%,8px);z-index:20;
    background:var(--ink);color:var(--paper);border-radius:4px;padding:9px 14px;
    font:12px ui-sans-serif,system-ui,sans-serif;max-width:min(420px,88vw);
    opacity:0;pointer-events:none;transition:opacity .18s,transform .18s;text-align:center}
  .flash.on{opacity:1;transform:translate(-50%,0)}

  dialog#shelfdlg,dialog#migratedlg{border:none;border-radius:5px;padding:0;
    width:min(560px,92vw);max-height:86vh;background:var(--paper);color:var(--ink);
    box-shadow:0 20px 60px rgba(35,32,26,.4)}
  dialog#shelfdlg::backdrop,dialog#migratedlg::backdrop{background:rgba(35,32,26,.45)}
  .shelflist{padding:12px 14px;overflow:auto;max-height:46vh;display:flex;
    flex-direction:column;gap:7px}
  .shelfrow{display:grid;grid-template-columns:1fr auto;gap:2px 10px;text-align:left;
    border:1px solid var(--rule);background:var(--paper2);border-radius:4px;
    padding:9px 11px;cursor:pointer;font:inherit;color:var(--ink)}
  .shelfrow:hover{border-color:var(--accent)}
  .shelfrow.current{border-color:var(--accent);background:rgba(63,107,79,.07)}
  .sname{font:600 13px ui-sans-serif,system-ui,sans-serif}
  .smeta{font:11px ui-sans-serif,system-ui,sans-serif;color:var(--ink3);text-align:right}
  /* The fill bar is the same fact the fore-edge shows, in a form you can read
     at a glance across every book on the shelf. */
  .sfill{grid-column:1/-1;height:3px;background:rgba(35,32,26,.10);border-radius:2px;
    overflow:hidden;margin-top:4px}
  .sfill i{display:block;height:100%;background:var(--accent)}
  .sfill{background:rgba(var(--edge),.12)}
  .shelffoot{padding:12px 14px 16px;border-top:1px solid var(--rule)}
  .mbtn.wide{width:100%;padding:9px;font-size:12.5px}
  .migrow{display:grid;grid-template-columns:auto 1fr auto;gap:9px;align-items:center;
    border:1px solid var(--rule);border-radius:4px;padding:8px 10px;cursor:pointer;
    font:12.5px ui-sans-serif,system-ui,sans-serif}
  .migrow em{font-style:normal;color:var(--ink3);font-size:11px}
  .migrow input{accent-color:var(--accent);width:16px;height:16px}
  .sizepick{display:flex;align-items:center;gap:9px;margin-bottom:10px;
    font:12px ui-sans-serif,system-ui,sans-serif;color:var(--ink2)}
  .sizepick select{flex:1;font:inherit;padding:6px 8px;border:1px solid var(--rule);
    border-radius:3px;background:var(--paper2);color:var(--ink)}
  .muted{color:var(--ink3);font-style:italic;font-size:12px}


  /* ---- the rings of a top-bound pad ---- */
  /* The wire sits ABOVE the leaves, so a page turning up passes behind it, the
     way it does on a real pad. */
  .rings{position:absolute;left:50%;top:calc(50% - var(--sh) / 2);
    width:var(--sw);height:26px;margin-left:calc(var(--sw) / -2);
    transform:translateY(-11px);pointer-events:none;z-index:8;display:none;
    background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='26'>\
<rect x='7.5' y='1.6' width='9' height='22.8' rx='4.5' fill='none' stroke='%238a8377' stroke-width='2.6'/>\
</svg>");
    background-repeat:repeat-x;background-position:center}
  .stage[data-binding=top] .rings{display:block}
  /* punched holes along the head of every leaf */
  .stage[data-binding=top] .page::before{content:'';position:absolute;
    left:0;right:0;top:0;height:calc(var(--phead) * var(--s));pointer-events:none;z-index:2;
    background-image:radial-gradient(circle at 12px 13px,
      rgba(35,32,26,.20) 0 4.4px,rgba(35,32,26,.06) 4.4px 5.6px,transparent 5.9px);
    background-size:24px 26px;background-repeat:repeat-x;background-position:center top}

  /* ---- full screen ---- */
  /* The page is WIDTH-limited on a phone, so zen has to give back horizontal
     room as well — dropping only the header and the tool strip would leave the
     page exactly the size it already was. */
  body.zen header,body.zen .strip{display:none}
  body.zen .book{padding:4px 5px 4px 4px;gap:4px}
  body.zen .foreedge{width:18px}
  body.zen .hint{display:none}
  .zenout{position:fixed;top:calc(8px + env(safe-area-inset-top));right:8px;z-index:30;
    width:30px;height:30px;border-radius:50%;border:1px solid var(--rule);
    background:var(--paper);opacity:.85;color:var(--ink2);cursor:pointer;
    font:13px ui-sans-serif,system-ui,sans-serif;line-height:1;padding:0}
  .zenout:hover{background:var(--paper);color:var(--ink)}

  @media(max-width:560px){
    .sub{display:none}
    .book{padding:6px 11px 6px 6px;gap:6px}
    .foreedge{width:24px}
    .strip{gap:3px;padding:5px 6px calc(5px + env(safe-area-inset-bottom))}
    .hud{right:50px}
    .strip{flex-wrap:nowrap;justify-content:space-between}
    .strip button{padding:5px 7px;font-size:10.5px;min-height:31px;min-width:31px}
    #more{display:inline-block}
    .sec{display:none;position:fixed;left:0;right:0;bottom:0;z-index:25;
      flex-wrap:wrap;justify-content:center;gap:5px;padding:11px 10px
      calc(11px + env(safe-area-inset-bottom));background:var(--paper);
      border-top:1px solid var(--rule);box-shadow:0 -8px 26px rgba(35,32,26,.16)}
    .sec.open{display:flex}
    .sec button{padding:7px 10px;font-size:11.5px;min-height:34px}
    .bookwarn{font-size:9.5px;padding:2px 5px}
    .sep{height:16px;margin:0 1px}
  }
  @media(prefers-reduced-motion:reduce){
    .leafwrap{transition:none}
  }
