:root{
 /* The palette below is dark, and the browser had no way to know it. Without
    this, `color-scheme` computes to `normal` and every control the UA draws
    itself -- the datetime calendar glyph, the number stepper, scrollbars, the
    picker popup -- is rendered from the LIGHT theme and dropped onto a #08060D
    field. The calendar icon on "Goes live" was a near-invisible dark-grey smudge
    for exactly this reason, and the fix is one declaration rather than a
    replacement control.

    It belongs on :root next to the palette: this is the same statement the
    palette makes, addressed to the UA instead of to the cascade. */
 color-scheme:dark;
 --ground:#08060D; --surface:#17151D; --raised:#201E27;
 --rule:#2A2833; --rule-hi:#3A3745;
 --text:#F5F7FF; --muted:#8C93A8; --dim:#5C6275;
 --accent:#12EAFF; --ok:#12FF88; --warn:#E8833A; --primary:#613CED; --primary-hi:#7B5BF0;
 /* Role colours, as tokens because the role is named in three places -- the top
    bar, the keys table, and the reveal panel -- and they were drifting: the
    table already painted org_admin lavender while the bar hardcoded orange for
    every role. Lavender rather than the primary purple: it has to read as text
    against a dark surface, which #613CED at 12px does not. */
 --role-super:#E8833A; --role-admin:#CBBEFF; --role-member:#8C93A8;
 /* The delta pair's down colour. #FF1249 is the green's hue-346 sibling --
    identical saturation and value, so up and down carry the same weight and
    neither shouts louder. Declared here with the rest of the palette rather
    than in two page sheets that each shipped their own identical copy. */
 --bad:#FF1249;
 --r:10px;
 /* The mono face, as a token.
    Twenty-four rules across five sheets already say
    `font-family:var(--face-mono,monospace)` -- the dock's clock, the links
    table's slug/dest/age columns, the panel's Created, the pagination footer --
    and NOTHING ever defined it. Every one of them silently took the fallback,
    so those elements rendered in the browser's default monospace while
    everything reaching for `.mono` got IBM Plex. Two mono faces on one page,
    from markup that looks equivalent.

    Defined here rather than by rewriting the consumers: the fallback shape is
    right, the token was simply missing its declaration. Keeping the literal
    fallback on this line means a sheet loaded without app.css still lands on a
    monospace face rather than inheriting Archivo. */
 --face-mono:"IBM Plex Mono",monospace;
 /* The UI face, for the same reason. Only .dopen reads it and its `inherit`
    fallback happened to land on Archivo anyway, so nothing looked wrong -- but
    the rule claimed a token that did not exist, which is how --face-mono went
    unnoticed through twenty-four sites. */
 --face-ui:Archivo,system-ui;
 /* 1600, which is _dock.js's DEFAULT_WIDTH and not the 1080 this sheet shipped.
    The dock offers 1320/1600/1920/none and writes its choice at runtime, so the
    reference has always rendered at 1600 and the CSS value was never seen.
    Porting drops the dock, which made 1080 the live width -- 24% narrower than
    anything the design was looked at in.

    1600 is a decision with a reason recorded next to it in _dock.js: the tighter
    shells left the Links table's destination column eliding URLs that had room
    to be read. The comment this replaces argued for a narrow column, which is a
    defensible position and not the one the design settled on. */
 --shell:1600px;
 /* bar1's height, which bar2 sticks below. _dock.js measures the bar and writes
    this at runtime, so in the reference the value is always right and the CSS
    fallbacks never apply. Nothing measures it here yet, which left both bars on
    their fallbacks -- and the two sheets disagree: _dock.css says 57px,
    _mobile.css says 55px. bar1 is 55px, so the 57px fallback won at desktop
    width and opened a 2px gap between the bars.
    Declared here so the correct value comes from CSS rather than from whichever
    fallback the cascade happens to reach. When the dock is wired it overwrites
    this with a measurement, which is the same number. */
 --bar1-h:55px;
 /* Derived, not observed: 55 + 45, the two bar heights. Overview has nothing
    that reads it -- _dock.css uses it to stick table headers, and Overview has
    no table with one. It stays arithmetic until Links renders a sticky header
    against it, and whoever gets there should measure rather than trust this.
    The reference's fallback is 113px, 13px off, which is what makes leaving it
    undeclared the wrong option. */
 --chrome-h:100px;
 /* The scope menu hangs from the chip's left edge, which is the shell's gutter.
    Measured 20px at desktop width, matching the fallback _scope.css carried --
    so unlike --bar1-h this one was never wrong, only undeclared. _scope.js
    remeasures at open, because the chip shifts right by the back-arrow's width
    for superadmin-in-an-org and the gutter is 12px on a phone. */
 --scope-x:20px;
 /* The gap between the chrome and a sticky table header. Separate from
    --chrome-h, which is a measurement of the bars that _dock.js overwrites on
    load -- folding a spacing choice into it would be undone the moment the dock
    ships. 6px: enough that the header reads as attached to its table rather
    than to the bar, without opening a stripe of ground between them. */
}
/* Paths point at fonts/, where this repo already vendors these three files. The
   reference kept them flat beside the stylesheet. The three woff2 are
   byte-identical to the vendored ones (cmp, all three), so this is a path
   change and not a substitution. */
@font-face{font-family:Archivo;src:url(fonts/archivo-var.woff2)format('woff2-variations');font-weight:400 700;font-stretch:100% 125%}
@font-face{font-family:"IBM Plex Mono";src:url(fonts/plexmono-regular.woff2)format('woff2');font-weight:400}
@font-face{font-family:"IBM Plex Mono";src:url(fonts/plexmono-medium.woff2)format('woff2');font-weight:500}
*{box-sizing:border-box}

/* Controls do not carry selectable text.
 *
 * A label on a button is a click target, not a passage to read: dragging across
 * "Create link" or double-clicking a nav item highlights it, which is the visual
 * language of selected prose appearing on something that is not prose. It also
 * makes a mis-drag leave a blue smear across the chrome that persists until the
 * next click.
 *
 * Scoped to the chrome and to controls, never to content. Slugs, destinations
 * and tag names in the table stay selectable, because copying one is a real
 * thing an operator does and the copy button is a convenience over it rather
 * than a replacement for it. So this lists the controls rather than setting
 * none on a container and clawing it back with `auto` -- an exception list is
 * how a table cell ends up unselectable two pages later.
 *
 * The reference already does this on details.adv summary, for the same reason:
 * that summary is a control whose text happens to be its label. */
.logo,.clock,.who,.btn,.scope,.dest a,.globebtn,.docsbtn,.slash,
.fpill,.fcheck,.fclear,.fcount,.tpage,.tsize label,.seeall,.rangebtn,
.st,.glyphs,.tile .lbl,.tile .sub,.chead .ctitle,.ctnoun
{-webkit-user-select:none;user-select:none}

body{background:var(--ground);color:var(--text);font:400 14px/1.5 Archivo,system-ui;font-stretch:100%;margin:0}
.mono{font-family:"IBM Plex Mono",monospace}
.shell{max-width:var(--shell);margin:0 auto;padding:0 20px}

.bar1{background:var(--surface);border-bottom:1px solid var(--rule)}
.bar1 .shell{display:flex;align-items:center;gap:14px;height:52px}
/* The wordmark goes home. It is an <a> rather than a span because that is the
   thirty-year-old convention for a logo in a top bar, and because it should
   land in a new tab on middle-click and offer "open in new tab" on right-click
   -- neither of which a click handler on a span gives you.
   The anchor's defaults have to be undone: colour is set per-glyph by .c and
   .p, and an underline would cut through the "//". */
.logo{font-stretch:125%;font-weight:700;font-size:17px;color:var(--text);
  text-decoration:none;display:inline-flex;align-items:center}
.logo:hover{text-decoration:none}
.logo:focus-visible{outline:2px solid var(--accent);outline-offset:3px;
  border-radius:3px}
.logo .c{color:var(--accent)} .logo .p{color:var(--primary-hi)}
.spacer{flex:1}

/* Visually hidden, still read aloud. Here rather than in a page sheet because
   four templates use it and the sheets defining it were not the sheets loading
   it: Keys used <span class="vh"> and loaded neither page-tags.css nor
   page-domains.css, so the text rendered at full size in the middle of the
   header -- 195px of "Show revoked and expired keys" sitting in a control meant
   to be an icon.
 *
 * Settings and Domains got away with it only because they happen to load
 * page-domains.css alongside their own sheet.
 *
 * clip-path rather than the older clip: clip is deprecated, and clip-path is
 * what page-domains.css already used of the two copies this replaces. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap}

/* The em-dash standing in for an absent value. Dim, because it is the absence
   of content rather than content -- an operator scanning a column should read
   past it, not stop on it.
 *
 * Here rather than in a page sheet because three sheets defined it under a
 * scoped selector -- `.num .zero`, `#estatetable .zero`, `.c-num .zero` -- and
 * none of them matches a tags cell. The Links table's dash was rendering at
 * full-strength #F5F7FF, inheriting from the cell, and looked deliberate only
 * because the tag chips beside it are brighter still. Measured. */
.zero{color:var(--dim)}
.clock{font-family:"IBM Plex Mono";font-size:12px;color:var(--muted)}
.who{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--muted)}
.role{color:var(--text);font-weight:600}
.fp{font-family:"IBM Plex Mono";font-size:11px;color:var(--dim);background:var(--raised);
 border:1px solid var(--rule);padding:2px 7px;border-radius:5px}
.btn{font:600 12.5px/1 Archivo;font-stretch:100%;color:var(--text);background:var(--primary);
 border:1px solid var(--primary);border-radius:7px;padding:8px 13px;cursor:pointer;text-decoration:none;display:inline-block}
.btn:hover{background:var(--primary-hi)}
.btn.ghost{background:transparent;border-color:var(--rule-hi);color:var(--muted)}

/* The API reference link in bar1, beside Sign out.

   SIZED TO SIGN OUT, NOT TO THE GLOBE. It started as a 34px square copied from
   .globebtn, on the reasoning that both are icon-only controls. That was the
   wrong peer: the globe sits in bar2 among other icon controls, and this sits
   in bar1 beside a 31px text button with nothing else its own shape nearby. A
   34px square next to a 31px button reads as a mistake rather than as a
   different kind of control, so it takes Sign out's height and radius and
   keeps only the glyph's own colours.

   SQUARE at Sign out's height -- 31x31, not the 28x31 it was briefly. A
   near-square reads as a square someone got slightly wrong; either commit to
   the shape or do not. The glyph grows with it, so the box gets bigger without
   the icon looking marooned in it.

   Negative margins on BOTH sides close the gaps around it. .bar1 .shell is a
   flex row with gap:14px, which is right between the clock, the role chip and
   a text button but too much around an icon control -- bar2 puts 9px between
   the globe and Create link for exactly that pairing. -5px each side trims the
   row's 14px to 9px here without moving anything else in the row, and keeping
   the two symmetrical is the point: an icon button with more air on one side
   than the other reads as badly placed rather than as deliberately grouped.

   Hover goes to --accent like the globe's, not to --warn: this opens a
   reference, and the amber in this palette means caution (.btn.danger) or
   superadmin (--role-super). */
.docsbtn{display:inline-flex;align-items:center;justify-content:center;
  width:31px;height:31px;margin:0 -5px;color:var(--muted);
  background:var(--surface);border:1px solid var(--rule-hi);border-radius:7px;
  cursor:pointer;flex:none;text-decoration:none}
.docsbtn:hover{color:var(--accent);border-color:rgba(18,234,255,.4);
  background:rgba(18,234,255,.07)}
.docsbtn:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* The destructive variant. Amber rather than red, matching --warn: red in this
   palette is reserved for something that has already gone wrong, and a button
   is about to do something deliberate.

   Lives here rather than in page-domains.css, where it started: the link
   panel's Delete confirm needs it and the Links page does not load that
   sheet, so the choice was one definition here or two that can drift. */
.btn.danger{background:var(--warn);border-color:var(--warn);color:#180C03}
.btn.danger:hover{background:#F09A55;border-color:#F09A55}
.btn.danger[disabled]{background:var(--raised);border-color:var(--rule-hi);
  color:var(--dim);cursor:not-allowed}

/* The copy variant, amber.

   THIS SHARES A HUE WITH .btn.danger AND IS NOT THE SAME CONTROL. The danger
   button is a filled amber block with near-black text, loud because it is
   about to do something that cannot be undone. This is an OUTLINE: amber text
   and border on the panel's own ground, filling only on hover. Weight is what
   separates them -- a filled block reads as a warning, an outline reads as an
   available action -- so the two never look alike even sitting in the same
   footer.

   Amber over the primary purple because Copy is the thing the panel is usually
   open for, and it sat in the same purple as Edit beside it: two equal-weight
   buttons where one is the common case and the other is not. */
.btn.copy{background:transparent;border-color:rgba(232,131,58,.55);color:var(--warn)}
.btn.copy:hover{background:rgba(232,131,58,.12);border-color:var(--warn)}
.btn.copy:focus-visible{outline:2px solid var(--warn);outline-offset:1px}
/* The copied confirmation. Green is what every other copy control in the dash
   flashes -- .dshort, .dcopy, .c-slug -- so the gesture reads the same here
   even though the resting colour is different. */
.btn.copy[data-copied="1"]{background:rgba(18,255,136,.12);
  border-color:rgba(18,255,136,.5);color:var(--ok)}

/* A page's own create action, sitting with the table it adds a row to.
   Create link owns the top bar on every page now, so the second action has to
   live where its effect is visible -- next to the rows it changes, not beside
   a control that does something else. Inherits .btn rather than restyling: one
   primary-action language, two positions. */
.pagenew{display:inline-flex;align-items:center;gap:6px;margin-bottom:11px;
  align-self:flex-start}
.pagenew svg{flex:none;opacity:.9}

/* Inside a card header it is a peer of the title, not a block above the table:
   the header supplies its own spacing, and .chead aligns on the baseline, which
   a padded button has no useful one of. Reset both rather than fork the class. */
.chead .pagenew{margin-bottom:0;align-self:center}

/* No margin rule here on purpose: .keys .chead and .domains .chead already set
   margin-bottom:11px, and the measured gap comes from those. Adding a
   :has(.pagenew) rule would have been dead weight on the two cards that have a
   button and a surprise on any other card that grows one. */

.bar2{border-bottom:1px solid var(--rule)}
/* 44px, down from 50. The tallest control in here is the search input at 37px, so
   50 spent 13px on padding around something that needs a few -- and this bar is
   permanent chrome on every page, so its height is a standing cost paid on every
   screen all day. 44 keeps 3-4px of air above and below the input and the nav
   items, enough that nothing touches the rules. */
.bar2 .shell{display:flex;align-items:center;gap:9px;height:44px}
.scope{display:flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--rule-hi);
 border-radius:8px;padding:6px 11px;font-size:12.5px;cursor:pointer;white-space:nowrap}
.scope .k{color:var(--dim);font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;font-weight:600}
.scope .caret{color:var(--dim);font-size:9px}
.dest{display:flex;gap:2px}
.dest a{color:var(--muted);text-decoration:none;font-size:13px;padding:7px 11px;border-radius:7px;white-space:nowrap}
.dest a:hover{background:var(--surface);color:var(--text)}
.dest a.on{background:var(--raised);color:var(--text);font-weight:600;box-shadow:inset 0 0 0 1px var(--rule-hi)}
.search{flex:1;position:relative;min-width:120px}
.search input{width:100%;font:inherit;font-size:13px;color:var(--text);background:var(--surface);
 border:1px solid var(--rule-hi);border-radius:8px;padding:8px 12px 8px 32px}
.search input::placeholder{color:var(--dim)}
.search .mag{position:absolute;left:11px;top:7px;color:var(--dim);font-size:13px}

/* --- search chips ---------------------------------------------------------
 *
 * A recognised key:value term is drawn as a token. The input keeps its own text
 * and stays the form's value -- it is rendered transparent, and a mirror div
 * behind it paints the same string with the chips wrapped in.
 *
 * Every property that affects text metrics has to match between the two or the
 * chips drift off the characters they wrap: font, size, padding, border width,
 * letter-spacing. They are set together here for that reason, not for tidiness.
 *
 * .chipped is added by searchchips.js, so a page whose script did not run keeps
 * a perfectly ordinary visible input. */
.search.chipped input{color:transparent;background:transparent;caret-color:var(--text);
  position:relative;z-index:1}
.search.chipped input::selection{background:rgba(18,234,255,.25);color:transparent}
/* Font, size and padding are NOT set here. A page sheet restyles the chrome's
   input -- measured at 12.5px with 8px 34px 8px 30px against this file's 13px
   and 8px 12px 8px 32px -- so any value written here is a guess at what the
   cascade decided. searchchips.js copies the input's own computed metrics onto
   this element instead, which is right by construction rather than by keeping
   two declarations in step. */
/* display:flex was here to centre the line vertically, and it cost 2px of
   accumulated horizontal drift: a flex container lays its text out differently
   from the input's normal inline flow, measured as scrollWidth 313 against the
   input's 315 on the same string.
 *
   Block flow with a line-height matching the content box centres the single
   line just as well and lays the text out the way the input does, which is the
   only thing that matters here. */
/* Transparent, and no border. The mirror paints TEXT; the input underneath is
   still a real field drawing its own background, border and radius.
 *
   It carried both at first, which made it an opaque panel covering everything
   between it and the input -- including the magnifier, which sits absolutely
   inside .search at the same stacking level. The icon measured fully visible
   the whole time: nothing hid it, the mirror was simply painted on top of it.

   The border-WIDTHS still come from the input via syncMetrics, because a border
   occupies space and the text origin sits inside it. Width without a colour
   offsets the text correctly and draws nothing. */
#qmirror{position:absolute;inset:0;overflow:hidden;white-space:pre;
  color:var(--text);background:transparent;border-style:solid;
  border-color:transparent;border-radius:8px;pointer-events:none}

/* The chip itself. Inline so it sits in the text flow at the exact width of the
   characters it replaces -- padding would push everything after it out of
   register with the input underneath.
 *
 * The tint and rounded corners read as a token without changing the advance
 * width, which is the one thing that must not change. */
/* Solid fill, no ring. The outlined version read as a box drawn around text;
   filled, it reads as one object -- which is what a chip is.
 *
   .22 rather than something heavier: the text sits ON this, and the cyan has to
   stay behind it rather than compete. The ring went because a fill and a border
   of the same hue is two statements of one thing, and the border was the half
   that made it look outlined.
 *
 * NO PADDING, NO MARGIN, NO BORDER. This element sits over a real <input>, and
 * every pixel of width it adds pushes the text after it out of register with
 * the characters underneath -- the caret then lands away from the glyph it
 * belongs to.
 *
 * Standing the fill off the glyphs was tried four ways and none survived
 * measurement. Recorded so the fifth attempt starts here rather than at the
 * beginning:
 *
 *   - `padding:2px 5px`: second chip 10.02px off, mirror 20px wider than the
 *     input.
 *   - Padding with a cancelling negative margin: layout width returns to zero,
 *     but the PAINTED box still grows. At 5px a side it covered the following
 *     word -- `clearance` ran into `winter` by 2.39px -- because the gap between
 *     two terms is one space, 2.63px in this face at this size.
 *   - Out-of-flow ::before fill: no layout cost at any inset, but two adjacent
 *     chips overlapped by 7.38px at 5px and 3.38px at 3px, and with equal
 *     translucent fills the seam vanished. Three chips rendered as one bar.
 *   - Widening the gap itself, via letter-spacing and then via an inline-block
 *     spacer: the first stretched the chip 48px because letter-spacing applies
 *     between every character rather than after the last; the second cancelled
 *     itself out and changed nothing.
 *
 * The constraint is structural, not a value to tune. The mirror must reproduce
 * the input's advance widths exactly, so the space between two terms is one
 * space, and a box wider than its text has nowhere to go. Real side padding
 * needs a token field with real chip elements -- and then the caret, selection,
 * undo, paste and IME behaviour become this file's problem, which is precisely
 * what the mirror exists to avoid. */
.chip{position:relative;border-radius:4px}

/* The fill, drawn OUT OF FLOW so the box can be bigger than the text.
 *
 * An absolutely positioned pseudo-element contributes nothing to layout, so the
 * chip still occupies exactly its characters' advance while the fill stands off
 * them on every side. That is the only way to get a box here: padding on the
 * chip itself adds width and pushes the following text out of register.
 *
 * -5px vertically: there is 11px of clearance above and below inside a 36.75px
 * field, so this axis is free.
 *
 * -2.4px horizontally. Both neighbouring fills paint into the gap between two
 * terms, so each side gets just under half of it -- and that gap is 5.23px now
 * rather than 2.63px, because chips are written two spaces apart. The extra
 * space is what bought this: at one space the ceiling was 1.3px, and 3px or 5px
 * made adjacent chips overlap by 3.38px and 7.38px until the seam vanished and
 * they rendered as one bar. */
.chip::before{content:"";position:absolute;inset:-5px -2.4px;z-index:-1;
  background:rgba(18,234,255,.22);border-radius:5px}
/* Colour only, NOT weight. font-weight:600 here made `tag:` wider than the
   regular-weight characters it sits over, and the error accumulates: measured
   at 0px on the first chip and 1.3px on the second, growing with each one until
   the caret visibly disagrees with the text.
 *
   Nothing inside the mirror may change a character's advance width. The cyan
   already separates the key from its value. */
.chipk{color:var(--accent)}

/* --- the key menu ---------------------------------------------------------
 *
 * Typing shows the keys still available. A hint, not a gate: the grammar is
 * parsed server-side, so this file being absent costs discoverability and
 * nothing else.
 *
 * Positioned against .search, which is already position:relative for the
 * magnifier. z-index above the table below it, and the same surface and radius
 * as the dock's panel so the chrome has one popover vocabulary. */
#qmenu{position:absolute;top:calc(100% + 5px);left:0;min-width:230px;z-index:40;
  background:var(--surface);border:1px solid var(--rule-hi);border-radius:9px;
  padding:4px;box-shadow:0 12px 28px rgba(3,2,7,.55);overflow:hidden}
.qopt{display:flex;align-items:baseline;gap:8px;width:100%;font:inherit;
  font-size:12px;color:var(--muted);background:none;border:0;border-radius:6px;
  padding:6px 8px;cursor:pointer;text-align:left}
.qopt:hover,.qopt.on{background:rgba(18,234,255,.10);color:var(--text)}
/* The key in the accent, matching the chip it becomes -- the menu shows the
   thing you are about to make, in the colour it will be. */
.qoptk{color:var(--accent);font-family:"IBM Plex Mono",monospace;font-size:11.5px}
.qopth{color:var(--dim);font-size:11px}

/* On a phone the menu follows the field's own state.
 *
 * It was hidden outright below 860px, on the reasoning that a popover anchored
 * to a 34px glyph would hang off the row. True while the field is COLLAPSED --
 * and it is expanded that matters, because that is when someone is typing and
 * has no other way to learn that tag: and dest: exist. The desktop hint is the
 * menu itself; there is nothing else to read.
 *
 * So: hidden while collapsed, shown while expanded. The expanded field spans
 * the row, so the menu spans it too rather than keeping its 230px desktop
 * minimum, which would have left it floating over a wider box. */
@media (max-width:860px){
  #qmenu{display:none}
  [data-search-open] #qmenu{display:block;left:0;right:0;min-width:0}
}

/* --- the chrome row must not run off its own end ------------------------
 *
 * v10.css and page-keys.css both set `.search{flex:0 0 auto;width:210px}`
 * unscoped, so they beat `.search{flex:1}` above on every page. The reference
 * is a static document at one width and was never resized with the field
 * focused. Measured at 900px on the Overview: nav 443 + search 210 + globe 34 +
 * Create 89 against an 885px bar -- Create sliced in half, search clipped
 * mid-placeholder, document scrolled sideways by 33px.
 *
 * Everything below is scoped to .bar2, so it outranks the page sheets without
 * editing the vendored v10.css and touches nothing outside the chrome row.
 *
 *   nav yields first, and SCROLLS rather than clips. It is the only item whose
 *     contents stay reachable when it shrinks, which keeps the two right-hand
 *     actions whole -- a half-drawn Create reads as broken in a way a scrolled
 *     nav does not.
 *
 *   overflow-x:auto, not hidden. Hidden moved the defect instead of fixing it:
 *     at 900px Create came whole but Audit was cut by 61px with nothing saying
 *     so. Auto is the state navscroll.js already watches, so the chevrons
 *     appear the moment the strip cannot fit.
 *
 *   search takes a 210px basis with a 120px floor, and its focus expansion is
 *     restated as flex-basis. That is the subtle half: a width is IGNORED on a
 *     flex item whose basis is set, so v10.css's `width:min(420px,38vw)` on
 *     :focus-within silently did nothing and the field sat at 210px when
 *     focused. It grows leftward because nav is the only flexible neighbour.
 *
 *   globe and Create never shrink. A button whose label clips is worse than one
 *     that pushes the nav.
 *
 * Above ~1000px none of it fires: the row fits at natural widths. */
.bar2 .navwrap,
.bar2 .dest{min-width:0}
.bar2 .dest{flex:0 1 auto;
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;
  -webkit-overflow-scrolling:touch}
.bar2 .dest::-webkit-scrollbar{display:none}
/* The transition is restated for the same reason the basis is: v10.css and
   page-keys.css both animate `width`, which is now the property that does not
   move. Same duration and easing, so the field opens exactly as it did before
   -- only the property carrying the change is different. */
.bar2 .search{flex:0 1 210px;min-width:120px;
  transition:flex-basis .16s cubic-bezier(.4,0,.2,1)}
.bar2 .search:focus-within{flex-basis:min(420px,38vw)}

/* Reduced motion drops the animation and keeps the resize, matching how the
   rest of the dash treats its transitions. */
@media (prefers-reduced-motion: reduce){
  .bar2 .search{transition:none}
}
.bar2 .globebtn,
.bar2 > .shell > .btn{flex:none}

/* The wrapper needs a box for the markers to anchor to, and the base layer
   gives it `display:contents` so it stays invisible to desktop layout. That was
   right when the nav could not scroll at desktop widths and is wrong now: a
   contents box generates no principal box, so it cannot position anything.
   navscroll.js toggles .at-start/.at-end on the wrapper at every width, so the
   chevrons are driven by one state rather than by the breakpoint. */
/* Plain selectors here, deliberately, and NOT guarded with
   `:root:not([data-search-open])`.

   That guard was an attempt to make this rule lose to the mobile layer's
   search-open hide, on the belief that app.css loaded after _mobile.css. It
   loads BEFORE it (layout.html:52 against :63), so the hide already wins on
   source order -- and the guard, at 0-4-0 against the hide's 0-3-0, was the
   thing that broke it. It left the strip visible with the field expanded and
   the search got 83px of a 375px row.

   The lesson is the ordering: _mobile.css is the last sheet and is meant to
   override, so a desktop rule here only needs to be no more specific than the
   mobile rule that has to beat it. */
.bar2 .navwrap{position:relative;display:flex;min-width:0;flex:0 1 auto}
.bar2 .navwrap > .dest{flex:0 1 auto}

/* The markers themselves, hoisted out of _mobile.css so both breakpoints share
   one definition. They were written inside the 860px query, which was correct
   while the strip only scrolled on a phone; re-stating 30 lines of gradient
   here to reach desktop would be two copies to keep in step, and the first
   divergence would be a chevron that points the right way on one breakpoint
   and the wrong way on the other.
   The width, the gradients and the per-side geometry are unchanged -- see the
   comments at their original site in _mobile.css for why 30px and why two
   gradients rather than one mirrored with transform. */
/* Plain here too, for the reason given on .navwrap above: _mobile.css is the
   later sheet and hides the markers with the field expanded, so this only has
   to avoid outranking that hide. */
.bar2 .navmark{
  position:absolute;top:0;bottom:0;width:30px;
  display:flex;align-items:center;
  border:0;padding:0;margin:0;cursor:pointer;
  font:inherit;font-size:15px;line-height:1;
  color:var(--muted);z-index:2}
.bar2 .nav-prev{left:0;justify-content:flex-start;padding-left:1px;
  background:linear-gradient(90deg,
    var(--bar2-bg, var(--surface)) 0%,
    var(--bar2-bg, var(--surface)) 40%,
    rgba(0,0,0,0) 100%)}
.bar2 .nav-next{right:0;justify-content:flex-end;padding-right:1px;
  background:linear-gradient(270deg,
    var(--bar2-bg, var(--surface)) 0%,
    var(--bar2-bg, var(--surface)) 40%,
    rgba(0,0,0,0) 100%)}
.bar2 .navmark:hover{color:var(--text)}
.bar2 .navmark:active{color:var(--text)}
/* Gone at the end you have arrived at, and gone entirely when nothing overflows
   -- a control that cannot do anything should not be on screen. */
.bar2 .navwrap.at-start .nav-prev,
.bar2 .navwrap.at-end .nav-next{display:none}

/* 22px top and bottom. The bottom was 60, reserved so the dock would not sit on
   the last row -- but the dock is position:fixed and never occupies document
   flow, so it could not have collided. Measured, it left 124px of empty space
   below every table on every page and at every width. */
main{padding:22px 0}
.tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.card{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r);padding:15px 17px}
.lbl{font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);font-weight:600}
.val{font-stretch:125%;font-weight:700;font-size:26px;line-height:1.14;margin-top:6px;font-variant-numeric:tabular-nums}
.sub{font-size:11px;color:var(--dim);margin-top:2px}

/* Tiles in a row are the same height whether or not they carry a subtext, so
   the ones without it have to fill that height rather than leave it blank
   under the number -- a tile with a gap where its neighbour has a line reads
   as one that failed to load its second line.

   Column flex with the value taking the free space: the label stays pinned to
   the top, the subtext (when there is one) stays pinned to the bottom, and the
   number sits centred in whatever is left. Tiles with and without a subtext
   then share the same label position and the same optical centre. */
.tile{display:flex;flex-direction:column}
.tile .val{flex:1 1 auto;display:flex;align-items:center}
.sub b{color:var(--accent);font-weight:400}

/* The label row: name on the left, delta chip on the right.
 *
 * baseline rather than center, so "SHORT URLS" at 10px and "-79.6%" at 11px sit
 * on one line rather than each centring in its own box -- the two have
 * different heights and centring them leaves the smaller visibly high.
 *
 * The chip is pushed right by justify-content rather than by a margin on the
 * label, so a tile with no chip at all (Tags, API keys) needs no different
 * rule: the label is simply the only child. */
.lblrow{display:flex;align-items:baseline;justify-content:space-between;gap:8px}

/* "week" in the sub-line is the control, not the percentage beside it.
 *
 * The percentage is the reading -- the thing the eye lands on -- and making it
 * clickable would put a pointer and a hover underline on the tile's most
 * important number, which reads as a link to somewhere else. The word answers
 * the question the reveal asks ("what was last week?"), so the affordance sits
 * on the noun.
 *
 * A dotted underline at rest, which is the established mark for "this word has
 * more behind it" and does not look like a link. It also means the control is
 * discoverable without hovering: a bare word in prose gives no sign it can be
 * pressed. */
.prevbtn{font:inherit;color:inherit;background:none;border:0;padding:0;
  cursor:pointer;line-height:inherit;
  text-decoration:underline dotted;text-underline-offset:2px;
  text-decoration-color:var(--muted)}
.prevbtn:hover{color:var(--text);text-decoration-style:solid}
.prevbtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
  border-radius:3px}

/* There is no .valbtn rule here any more, and the absence is the point.
 *
 * The Clicks value was a button that swapped "717K" for "717,014" on click.
 * Both headline tiles print their windowed figure in full now, so there was
 * nothing behind the press and the button, its dotted underline, exact.js and
 * the data-exact attribute all came out together.
 *
 * It is recorded because the rule it needed was subtle and would be rewritten
 * the same wrong way if the control ever returns: `font: inherit` on a button
 * inside .val resets font-size to the TILE's 14px, since the shorthand sets
 * every font property rather than only the family. It rendered a 14px number
 * beside a 26px neighbour, visibly small and high in a paired row, and no DOM
 * assertion caught it -- only a screenshot did. font-family alone is what such
 * a button needs; everything else is already correct from .val. */

/* The all-time figure in the tile's top-right corner.
 *
 * The one number on the tile that does not move with the range control, which
 * is why it is labelled and why it sits apart from the windowed value rather
 * than under it. Rounded ("1.5M") because the slot is narrow, and rounded is
 * not estimated -- the figure behind it is a real count.
 *
 * Sized at 11px to sit under the 10px label without competing with it, and
 * tabular-nums so the corner does not reflow between ranges. */
.lifetime{font-size:11px;color:var(--muted);font-weight:600;white-space:nowrap;
  font-variant-numeric:tabular-nums}
/* The qualifier, dimmer than the figure it names and not tabular because it is
   words. Uppercased to read as a label rather than as part of the number. */
.lifetimek{color:var(--dim);font-weight:500;font-size:9px;letter-spacing:.09em;
  text-transform:uppercase;font-variant-numeric:normal;margin-left:1px}

/* Last week's figure, revealed after the word that was clicked and removed a
 * few seconds later.
 *
 * Inherits the sub-line's 11px rather than setting its own size: it is part of
 * that sentence, not an annotation on the value above it. Brighter than the
 * surrounding prose, because the number is the thing being asked for. */
.prevnote{color:var(--text);white-space:nowrap;
  font-variant-numeric:tabular-nums;
  opacity:1;transition:opacity .26s ease}
.prevnote.fading{opacity:0}

/* The reveal is a spot check, and a fade is how it says "this was temporary".
   Reduced motion keeps the removal and drops the transition -- the note still
   disappears, it just does not animate on the way out. */
@media (prefers-reduced-motion: reduce){
  .prevnote{transition:none}
}
.duo{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
/* gap belongs with the flex declaration, not in two page sheets. v10.css and
   _shared-ref.css each carried `.chead{gap:10px}` and Keys loads neither, so
   its header ran its children edge to edge: the reveal toggle sat at a 0px gap
   from Create key, touching the page's one mutation. Measured.

   10px is the value both copies already used; they are now redundant rather
   than contradicted. */
.chead{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.ctitle{font-stretch:125%;font-weight:600;font-size:13px}
.cmeta{font-size:11px;color:var(--dim)}
svg.g{display:block;width:100%;height:auto;margin-top:8px}
.gline{fill:none;stroke:var(--accent);stroke-width:1.6}
.gline2{fill:none;stroke:var(--primary-hi);stroke-width:1.6}
.tick{stroke:var(--rule);stroke-width:1;stroke-dasharray:2 4}
.tickl{fill:var(--dim);font-size:8.5px;font-family:"IBM Plex Mono"}
/* The share bar, shared. It began on the Tags table and the Overview's
   breakdown panels want the same thing, so it lives here rather than being
   copied -- one bar language, one place to change it. Always scaled against
   the largest row, never against 100%: at the spreads this data actually has,
   a percent-of-total scale leaves every row but the first invisible. */
.bar{flex:1 1 auto;min-width:0;height:5px;background:var(--ground);
  border:1px solid var(--rule);border-radius:2px;overflow:hidden}
.bar i{display:block;height:100%;min-width:2px;background:var(--accent);
  opacity:.8;border-radius:1px}

.recent{margin-top:12px}
/* The recently-created table while fillrecent.js is fetching the rows that fit.

   A dim, not a spinner. The table already holds ten real rows at this point --
   the fetch is asking for MORE, not for the first ones -- so covering readable
   content with a loading symbol would hide data to announce that more data is
   coming. Dimming says "settling" while leaving every row legible.

   Fast enough to be invisible on a local fetch, which is the point: it only
   shows up when the request is slow enough that the reader would otherwise
   wonder whether the resize registered. pointer-events stays on, because the
   copy buttons on the existing rows still work. */
tbody[data-filling]{opacity:.55;transition:opacity .12s ease}
@media (prefers-reduced-motion: reduce){tbody[data-filling]{transition:none}}
table{width:100%;border-collapse:collapse}
th{font-size:9.5px;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);text-align:left;
 font-weight:600;padding:0 9px 8px;font-stretch:125%}
td{padding:8px 9px;border-top:1px solid var(--rule);font-size:12px;vertical-align:middle}
.c-time{color:var(--dim);font-size:11px;width:1%;white-space:nowrap}
.c-slug{color:var(--accent);text-decoration:none;white-space:nowrap}
/* The host is dimmed and the code is lit, on every page that shows a short URL.
   `jmp.st/` is identical on every row and identifies nothing; the code is the
   link. Lighting both spends the accent on the repeated half and turns a column
   of slugs into one block of colour instead of a list of distinct codes.
   This lived in page-links.css, so the Overview -- which renders the same value
   from a different builder -- had no rule and came out fully cyan. */
.c-slug .pre{color:var(--dim)}
.c-dest{color:var(--muted);font-size:11px}
/* THE OPEN COLUMN on the Overview's recently-created table.
   Sized to its glyph and nothing more -- the arrow is the whole content, so a
   wider column would take space from Destination, which is the cell that
   actually runs out of room. width on the header is what browsers honour for a
   table column; the cell just centres in it. */
.th-go{width:34px}
.c-go{text-align:center;padding-left:0;padding-right:6px}
/* Dim until the row is approached, so ten arrows do not compete with the data
   they sit beside. The row hover is what reveals them, which is also the cue
   that the whole row is the thing being acted on. */
/* CYAN AT REST, not on hover only.
   These were --dim lifting to --accent, which kept ten arrows quiet but also
   made the one control on the row read as disabled until touched. --accent is
   the same cyan the short-URL slugs carry, so the column reads as the row's
   other interactive element rather than as decoration; the hover state now
   changes the ground rather than the colour. */
.rowgo{display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;border-radius:5px;color:var(--accent);
  text-decoration:none;transition:background .12s,opacity .12s;opacity:.75}
tr:hover .rowgo{opacity:1}
.rowgo:hover{opacity:1;background:var(--ground)}
.rowgo:focus-visible{outline:2px solid var(--accent);outline-offset:1px;
  color:var(--accent)}
.tag{display:inline-block;font-family:"IBM Plex Mono";font-size:10px;padding:1px 6px;border:1px solid;border-radius:5px}
.num{text-align:right;font-variant-numeric:tabular-nums;width:1%}
/* The dot reports a fact rather than decorating: it pulses only while the feed
 * is actually connected, goes solid amber when a poll is in flight, and stops
 * dead on failure. A always-on "live" light that keeps blinking after the
 * connection drops is worse than no light at all. */
.live{display:inline-flex;align-items:center;gap:7px;font-size:10px;color:var(--dim);
 text-transform:uppercase;letter-spacing:.09em;font-weight:600}
.live i{width:6px;height:6px;border-radius:50%;background:var(--ok);flex:none}
.live[data-state="ok"] i{animation:beat 2.4s ease-in-out infinite}
.live[data-state="ok"]{color:var(--text)}
.live[data-state="busy"] i{background:var(--warn);animation:none}
.live[data-state="busy"]{color:var(--muted)}
.live[data-state="down"] i{background:var(--dim);animation:none;box-shadow:none}
.live[data-state="down"]{color:var(--dim)}
@keyframes beat{
  0%,100%{opacity:.32;box-shadow:0 0 0 0 rgba(18,255,136,0)}
  50%{opacity:1;box-shadow:0 0 0 3px rgba(18,255,136,.13)}
}
@media (prefers-reduced-motion: reduce){
  .live[data-state="ok"] i{animation:none;opacity:1}
}

/* ---- modal: create link ---- */
.scrim{position:fixed;inset:0;background:rgba(4,3,8,.72);z-index:400;
 display:flex;align-items:flex-start;justify-content:center;padding:7vh 20px 20px;overflow-y:auto}
.scrim[hidden]{display:none}
.modal{width:100%;max-width:560px;background:var(--surface);border:1px solid var(--rule-hi);
 border-radius:14px;box-shadow:0 24px 60px -12px rgba(0,0,0,.7)}
.mhead{display:flex;align-items:center;justify-content:space-between;
 padding:16px 18px;border-bottom:1px solid var(--rule)}
.mtitle{font-stretch:125%;font-weight:600;font-size:15px}
.mclose{background:transparent;border:0;color:var(--dim);font-size:20px;line-height:1;
 cursor:pointer;padding:2px 6px;border-radius:6px}
.mclose:hover{color:var(--text);background:var(--raised)}
.mbody{padding:18px}
.f{margin-bottom:13px}
.f label{display:block;font-size:11px;letter-spacing:.07em;text-transform:uppercase;
 color:var(--muted);font-weight:600;margin-bottom:5px}
/* min-width:0 alongside width:100%, and both are needed.
 *
 * width:100% sizes the input to its column; min-width:0 is what allows that
 * column to be narrower than the control's own intrinsic minimum. A
 * datetime-local is a UA-drawn widget with real segments, so without this it
 * refuses to shrink and pushes its grid column -- and the dialog -- wider than
 * the space available. The wrapper's min-width:0 in .f2 is only half the fix;
 * the input needs it too, or the item shrinks and its contents do not. */
.f input,.f textarea,.f select{width:100%;min-width:0;font:inherit;font-size:13px;
 color:var(--text);
 background:var(--ground);border:1px solid var(--rule-hi);border-radius:8px;padding:9px 11px}
.f input::placeholder,.f textarea::placeholder{color:var(--dim)}
/* No spinner on the numeric fields.

   The stepper is UA chrome: a grey double-arrow that ignores the palette, sits
   flush against a border it does not match, and appears only on hover or focus
   so the field changes width-of-content the moment it is used. It also invites
   the one interaction nobody wants on a visit cap -- clicking to 251 -- while
   the keyboard already offers arrow keys for anyone who does.

   The input stays type=number. That is what carries the constraint: min=1 is
   enforced by the browser, the value is validated server-side regardless, and
   phones still get the numeric keypad. Only the drawn control goes; the
   semantics do not. Switching to type=text to lose the spinner would trade a
   cosmetic problem for a real one.

   Unscoped by container. The visit cap exists on two surfaces -- the create
   dialog's `.f` and /admin/new's `.field` -- and scoping this to `.f` would fix
   the dialog while leaving the full page with a spinner nobody chose. There is
   no third styling of a number input to be surprised by: these are the only two
   in the dash. */
input[type=number]{-moz-appearance:textfield;appearance:textfield}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.f input:focus-visible,.f textarea:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.f .hint{font-size:11px;color:var(--dim);margin-top:4px}
/* Two fields side by side, and the min-width:0 is what stops them overflowing.

   A grid item defaults to min-width:auto, which means "never shrink below your
   content's intrinsic minimum". For a text input that is nothing; for
   datetime-local it is a UA-drawn control with a real minimum -- three spin
   segments, separators and a picker icon -- so 1fr stopped being a limit and
   both columns pushed past the dialog's edge. Goes live and Expires are the
   two that show it, because they are the only datetime pair in either form.

   min-width:0 says the column may shrink and let the input scroll its own
   content, which is what 1fr was already promising.

   Same fix .bar2 .dest and .shell already carry for the same reason. The
   overflow appeared in BOTH dialogs at once, which is the tell that it belongs
   here rather than in either one.

   auto-fit with a floor rather than a hard 1fr 1fr: below that width the two
   datetime controls cannot sit side by side without clipping their own
   segments, and stacking is better than a field you cannot read.

   168px, and the number was measured rather than picked. The dialog's inner
   width is viewport - 40 (scrim padding) - 2 (modal border) - 36 (mbody
   padding), so a 390px phone leaves 312px. At a 150px floor that is two
   columns of exactly 150px -- it fit by ONE pixel, and a datetime-local needs
   about 140 before padding, so the control would have been clipped or
   scrolling its own segments on the commonest phone size there is.

   At 168 every phone from 320 to 414 stacks, and 430 upward keeps two columns
   at 170px or wider. Whichever side of the threshold a device lands on, it
   lands there deliberately. */
.f2{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:11px}
.f2>*{min-width:0}
/* A value shown but not editable: the edit dialog's short link and domain.

   Three properties, and each is doing a different job:

     - dimmed, so it reads as context rather than as a field awaiting input;
     - pointer-events:none, so a click lands on the row instead of putting a
       caret in a box that will not take one;
     - default cursor, because a text cursor over an uneditable box is the
       thing that invites the click in the first place.

   The element stays readonly rather than becoming disabled. It carries no
   name, so it is never submitted either way -- and readonly leaves the value
   reachable to a screen reader, where disabled would take it out of the
   accessibility tree entirely. The point is that the value IS shown; it just
   cannot be changed here.

   user-select:text so the value can still be highlighted and copied by anyone
   who wants it. Denying the click is about refusing an edit, not about
   withholding the string. */
.locked{opacity:.55;pointer-events:none;cursor:default;user-select:text}
/* The short link in the edit dialog, in the accent the panel gives a slug.

   The whole field rather than just the slug half: this is an <input> holding
   "host/slug" as one string, and an input cannot paint part of its value the
   way the panel's two-span button can. Accenting all of it is the closer
   reading of the two -- the alternative is a link identifier in body text,
   which is what every other panel stopped doing.

   Set alongside .locked, which dims it, so the accent arrives already muted to
   55% and reads as a locked identifier rather than as a live control. */
.elslug{color:var(--accent)}

/* A field a 301 forbids: the window's two ends and the visit cap.

   Faded and non-interactive rather than hidden. Hiding them would make the
   form change shape on every Redirect change and leave a reader wondering
   whether the fields still exist; fading says "these are here and not
   available right now", which is the true statement.

   The inputs are also disabled by script, so this is the visible half of a
   state the form already enforces -- and a disabled input is not submitted,
   which is what makes choosing 301 mean "clear the window and cap" rather
   than "send them and be refused".

   This class was set by editlink.js from the day it was written and styled
   nowhere, so the fade it names never rendered. */
.f.is-off{opacity:.45}
.f.is-off input,.f.is-off select{pointer-events:none}
@media (prefers-reduced-motion: no-preference){
  .f{transition:opacity .12s}
}
.mono-in{font-family:"IBM Plex Mono",monospace}
/* --- the dialogs' tabbed lower half --------------------------------------
 *
 * Destination, slug and tags stay pinned above this: the common path is paste
 * a URL and submit, and that must not require picking a tab first. Everything
 * else splits in two -- Details is what the link is CALLED (title, notes) and
 * Advanced is how it BEHAVES (domain, redirect, window, cap, fallback).
 *
 * DETAILS LEADS AND IS THE DEFAULT. Noting what a link is for is the far
 * commoner act: capping it, rescheduling it or changing its redirect type are
 * things a link needs occasionally, and a title or a note is something an
 * operator writes most times they touch one. Opening on the rarer half put the
 * two optional text fields behind a click on the path most often taken.
 *
 * This replaces a <details class="adv"> disclosure holding all of it in one
 * lump. A disclosure answers "is there more?"; it does not answer "where is
 * the thing I came for", which is the question once the hidden half holds two
 * unrelated kinds of field.
 *
 * DRIVEN BY RADIOS, NOT SCRIPT. The inputs sit before the panels and a
 * :checked ~ sibling selector shows the matching one, so the tabs work with
 * JavaScript absent -- same reason these dialogs are server-rendered at all.
 * The radios are visually hidden rather than display:none, because a
 * display:none input is not focusable and the tabs would leave the keyboard.
 *
 * BOTH the tab highlight and the panel reveal reach with `~`, not `+`. The
 * radios are grouped at the top of .mtabwrap so one selector can reach the
 * panels, which puts the tab strip a sibling or two away rather than adjacent
 * -- an earlier `:checked + label` matched nothing at all, and the active tab
 * rendered identically to the inactive one while the panels switched
 * correctly.
 *
 * The radio NAMES are per-dialog (cl-tab / el-tab) so create's tabs and edit's
 * do not join one group -- both dialogs exist in the same document on the
 * Links page, and a shared name would make selecting a tab in one clear the
 * other's. They carry form="none" so the name never reaches the POST body;
 * see the note in createlink.html.
 */
.mtabs{display:flex;gap:2px;margin-top:2px;border-bottom:1px solid var(--rule)}
.mtabs label{font-size:12px;font-weight:600;color:var(--muted);cursor:pointer;
 padding:8px 12px;border-bottom:2px solid transparent;margin-bottom:-1px;
 user-select:none;transition:color .12s,border-color .12s}
.mtabs label:hover{color:var(--text)}
/* The visually-hidden radio. Kept in the layout at 1x1 rather than removed, so
   it can hold focus; the ring is drawn on the label instead, below. */
.mtabin{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
 overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
#cl-tab-details:checked ~ .mtabs label[for="cl-tab-details"],
#cl-tab-adv:checked     ~ .mtabs label[for="cl-tab-adv"],
#el-tab-details:checked ~ .mtabs label[for="el-tab-details"],
#el-tab-adv:checked     ~ .mtabs label[for="el-tab-adv"]{
 color:var(--text);border-bottom-color:var(--primary-hi)}
#cl-tab-details:focus-visible ~ .mtabs label[for="cl-tab-details"],
#cl-tab-adv:focus-visible     ~ .mtabs label[for="cl-tab-adv"],
#el-tab-details:focus-visible ~ .mtabs label[for="el-tab-details"],
#el-tab-adv:focus-visible     ~ .mtabs label[for="el-tab-adv"]{
 outline:2px solid var(--accent);outline-offset:-2px;border-radius:4px}
/* Panels are hidden by default and revealed by the checked radio. The selector
   reaches across the tab strip to the panel, so the radios must precede both --
   see the markup, where all of them sit at the top of .mtabwrap. */
.mtabpanel{display:none;padding-top:14px}
#cl-tab-details:checked ~ .mtabpanels #cl-panel-details,
#cl-tab-adv:checked     ~ .mtabpanels #cl-panel-adv,
#el-tab-details:checked ~ .mtabpanels #el-panel-details,
#el-tab-adv:checked     ~ .mtabpanels #el-panel-adv{display:block}
.swatchrow{display:flex;gap:9px;align-items:center}
.swatchrow input[type=color]{width:40px;height:36px;padding:2px;background:var(--ground);
 border:1px solid var(--rule-hi);border-radius:8px;cursor:pointer}
.mfoot{display:flex;align-items:center;gap:9px;padding:14px 18px;
 border-top:1px solid var(--rule);background:var(--ground);border-radius:0 0 13px 13px}
.mfoot .spacer{flex:1}
/* The unfurl preview's rules were here -- .ogprev, .ogbar, .ogtitle, .ogdesc
   and .ogurl -- along with the rest in _createlink.css, which is deleted. No
   template rendered any of them, because the OG fields they previewed have no
   columns behind them and the redirect path has no HTML response on a live link
   to carry an og: tag in. A stylesheet is a claim about what the UI does; these
   nine selectors claimed a feature that has never existed.

   .swatchrow above is the same story -- it styles the card-colour picker, which
   no template renders either. Left in place deliberately rather than swept up
   with these: it is out of the scope this change was asked for, and it is the
   one of the group that a future colour input of any kind would reuse. */
