/* app-search.css — THE SEARCH SURFACE'S OWN CHROME (the eleventh part, added 2026-08-21).
 *
 * ⚖ WHY THIS FILE EXISTS AT ALL, AND IT IS NOT "app.css WAS FULL". It was — 999 of its own
 * 1,000-line ceiling, exactly as forage-nly predicted — but a new part is justified by what
 * lives here, not by where there was room. The search surface (docs/DECISIONS.md "Tags and text
 * are separate haystacks", owner rulings 2026-08-20) is one coherent, growing component: a
 * query box that renders the query as PILLS, a narrowing drawer, and the suggestion list that
 * teaches the grammar. It is now the biggest single feature in the archive and it will keep
 * growing; wedging it into the base sheet would have put it back over the ceiling within a week.
 *
 * ⚠ CASCADE POSITION IS AFTER app-builder.css AND BEFORE app-viewer.css. It must come after
 * app.css (it amends `.searchbar`, whose base rule lives there) and it must NOT come after
 * app-viewer.css, which carries "THE LAST WORD ON MODIFIER VERBS" and is last by law. The
 * position is pinned in server/tests/cssparts.js and asserted against every page's <link> tags
 * in both directions, so this file cannot be added and left unlinked on some surface.
 *
 * ⚖ THE 44px FLOOR IS SPENT AS AN INVISIBLE OVERHANG HERE, NEVER AS DRAWN SIZE — docs/DECISIONS.md
 * "The floor may not change what a member can see" (owner ruling 2026-08-20). Every control in
 * this file draws tight and carries a `::after` sized from --tap-min. Two consequences that are
 * arithmetic rather than taste, and that a later tidy-up will break if it does not read this:
 *   · THE OVERHANG IS VERTICAL ONLY. Horizontal tightening is free; a horizontal halo steals the
 *     tap from the control beside it, which is the mis-tap TAP_GAP exists to stop.
 *   · A WRAPPING row of 44px targets needs a ROW gap of (44 − drawn height) or the two rows'
 *     hit boxes overlap. That is why `.sq-opts` below carries a 16px row gap against a 3px
 *     column gap. The asymmetry is load-bearing. Squeeze the row gap and you break taps, not
 *     looks — the same arithmetic app.css's `.sortbar` already spends.
 */

/* ═══ THE QUERY BOX — the query, rendered ══════════════════════════════════════════════════
   ⚖ THE BOX SCROLLS, IT DOES NOT WRAP (owner ruling 2026-08-20). A wrapping pill row needs a
   19px row gap to keep its hit boxes from overlapping, and that is EXACTLY the canyon that
   stranded `shuffle` under the sort bar and started this whole pass. One line, at every width;
   the count on the narrow button covers anything scrolled out of sight. */
/* ⚖ THE WRAPPER IS A COLUMN AND THE BOX FILLS IT (forage-h5v, 2026-08-22) — so THE DRAWN BOX
   IS ALWAYS THE ROW'S OWN HEIGHT. `.searchbar` is `align-items:stretch`, so `.sqwrap` already
   stretched to the flex line; `.sqbox` was an ordinary block inside it and kept its own content
   height at the TOP of that wrapper. While the submit button carried a `min-height:44px` the
   line was 44px and the box drew 40.8, so the BUTTON HUNG 3.2px BELOW THE BOX IT BELONGS TO and
   their centres were 1.6px apart — measured at 390, 360 and at 1280 with a coarse pointer,
   which is the state the owner photographed. ⚠ It also detached `.sqsugg`, which hangs off
   `calc(100% + 2px)` of the WRAPPER: the suggestion list floated 5.2px under the box instead of
   2px. The button's floor is spent as `.btn::after` now (app.css, and that is this repo's own
   ruling), so today both sides are 40.8px — and this rule is what stops them coming apart again
   from the OTHER direction the next time something on this row grows. */
/* ⚖ AND THE BUTTON BORROWS THE BOX'S HAIRLINE, so the pair reads as ONE control at every
   pointer (forage-h5v). Everywhere else `.btn` is a standalone control and `--hair` is right for
   it; here it stands 8px from a field drawn in `--hair-hi` and was measurably the fainter of the
   two boxes — rgb(36,42,51) against rgb(57,66,78). ⚑ At a FINE pointer, where the heights already
   agreed, this was the only thing wrong with the pair. ⚠ IT LIVES HERE AND NOT BESIDE
   `.searchbar .btn` IN app.css for two reasons: app.css was at its 1,000-line ceiling (repealed 2026-08-29), and this
   sheet is the one that amends `.searchbar` (see the header). ⚠ THE HOVER RULE HAS TO BE
   RESTATED — `.btn:hover` is the same specificity and this sheet is LATER in the cascade, so
   without the second line the border would stop reacting to hover altogether. */
.searchbar .btn{border-color:var(--hair-hi)}
.searchbar .btn:hover{border-color:var(--accent)}
.sqwrap{position:relative;flex:1;min-width:0;display:flex;flex-direction:column}
.sqbox{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:4px;align-items:center;flex:1;
  border:1px solid var(--hair-hi);background:var(--sunk);padding:2px 4px;cursor:text;
  scrollbar-width:none}
.sqbox::-webkit-scrollbar{display:none}
.sqbox>*{flex:none}
/* ⚠ 16px KEEPS iOS FROM ZOOMING THE PAGE ON FOCUS — the same reason .searchbar input carries it
   in app.css. Anything smaller here and the viewport jumps on every tap into the box. */
.sqbox input{flex:1 0 88px;min-width:88px;background:none;border:0;color:var(--ink);
  font:inherit;font-size:16px;padding:5px 2px;outline:none}
.sqbox input::placeholder{color:var(--faint)}

/* ═══ A PILL IS ONE CRITERION, AND IT WEARS ITS OWN GRAMMAR ═══════════════════════════════
   The symbol is the STRENGTH (^ prefer · + only · − exclude) and the sigil is the KIND
   (# tag · @ uploader · a scope mark for text). Both are drawn, never implied: the whole point
   of the separation is that you can see which haystack you searched. */
.sqpill{display:inline-flex;align-items:center;gap:3px;position:relative;white-space:nowrap;
  border:1px solid var(--hair);background:var(--raised);color:var(--ink);
  font:inherit;font-size:10.5px;line-height:1;padding:4px 6px 4px 4px;cursor:pointer;
  letter-spacing:calc(.02em * var(--track))}
/* ⚠ A LONG-PRESS TARGET MUST REFUSE THE OS TEXT SELECTION or the gesture selects the label and
   pops the native callout menu instead of toggling fuzzy. BOTH properties are required —
   Safari honours -webkit-touch-callout, everything else honours user-select. */
.sqpill{user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}
.sqpill .sy{font-size:12px;line-height:1;width:9px;text-align:center;font-weight:700}
.sqpill .sg{color:var(--faint);font-weight:700;margin-right:-2px}
.sqpill .st{color:var(--info);font-weight:700;margin-right:-2px}
/* ⚖ THE KIND MARK ON A PILL IS THE MARK THE SUGGESTION ROW WEARS (F19, owner ruling 2026-09-03,
   looking at his own live pill line — `^ # orange_cat` beside `^ ≡ car`: *"why do the hashtag
   icons render like that all small."* The `#` was `.sg`: a --faint text character at the 10.5px
   LABEL's size, next to a text pill already wearing a full 13px scopeMark. Same defect as
   finding 1, one surface over — §1.1 lists the pill under "where the tag mark appears" and F1
   only landed it on the ROW.
   ⚖ SAME CLASS, SAME BUILDER as `.sqsugg .vs` (`sqKindHead`, page-index-search.js): one mark
   per kind, changeable in one place. Sized to THIS surface's mark box — 13px, exactly the
   `.sqpill .sci` below — so the tag pill and the text pill are one height and ⛔ NOTHING here
   grows past the `≡` beside it.
   ⚠ THE STRENGTH GLYPH IS UNTOUCHED. `.sy` keeps its own 12px and its own state colour (§1.2:
   the kind is the MARK, the strength is the PAINT); only the kind mark grew.
   ⚠ `margin-right:-2px` against the pill's own 3px gap IS the 1px weld, so `#orange_cat` reads
   as one word the way `#cat` does on the row.
   ⚠ `.sg` STAYS ABOVE — clubs-chat.js's react sheet hand-draws a static pill with it. */
.sqpill .vs{display:inline-flex;align-items:center;gap:1px;flex:none;min-height:13px;
  font-size:12px;line-height:1;font-weight:700;margin-right:-2px;letter-spacing:normal}
.sqpill.k-tag .vs{color:var(--accent)}
.sqpill.k-by .vs{color:var(--bright)}
.sqpill.is-prefer{border-color:var(--hair-hi)}
.sqpill.is-prefer .sy{color:var(--accent-deep)}
.sqpill.is-only{border-color:var(--accent-deep);color:var(--accent)}
.sqpill.is-only .sy,.sqpill.is-only .sg,.sqpill.is-only .vs{color:var(--accent)}
.sqpill.is-excl{border-color:var(--bad);color:var(--bad)}
.sqpill.is-excl .sy,.sqpill.is-excl .sg,.sqpill.is-excl .vs{color:var(--bad)}
.sqpill.is-fuzzy{border-style:dashed}
.sqpill.k-text.is-only{border-color:var(--info);color:var(--info)}
.sqpill.k-text.is-only .sy{color:var(--info)}
/* ⚖ THE SCOPE MARK'S BOX, AND THE CALLER OWES IT ONE (forage-8mj). Every node from
   common-icons.js carries a viewBox and NO width, so an <svg> with no box computes to 100% wide
   — the shape a mark ships INERT rather than wrong. 13px is the size the registry's own header
   argues for beside a 10.5px pill label; `.navi`'s 14px is deliberately not reused, because
   that class also brings the masthead's paint. navicons.test.js requires both dimensions the
   moment `scopeMark(` appears in web/, so this rule is a gate, not decoration. */
.sqpill .sci{width:13px;height:13px;flex:none;margin-right:1px}
/* `related:<post-id>`'s value is a bare NUMBER, so the mark is the only thing on the pill that
   says what the number is. It rides the pill's state colour like every other glyph here. */
.sqpill.k-related{font-variant-numeric:tabular-nums}
/* the hold ramps the border over exactly the long-press window, so the press is visibly
   building rather than feeling like nothing happened until the finger lifts. */
.sqpill.holding{border-color:var(--accent);transition:border-color .45s linear}
@media(prefers-reduced-motion:reduce){.sqpill.holding{transition:none;opacity:.66}}

/* ═══ AN EMPTY ANSWER THAT KNOWS WHY (forage-8mj) ══════════════════════════════════════════
   `similar:` means NEARLY THE SAME PICTURE and is empty for ~96% of posts — true of the archive,
   not a failure — so the empty state says so and offers `related:` as a runnable query. It sits
   IN the result column, under the count, exactly where "nothing matches that." sits.
   ⛔ NOT `.notice`, which is the floating toast (app-builder.css, `position:fixed` at the toast
   z-index). The first cut of this borrowed that class and drew the explanation as an overlay
   pinned over the grid; the unit tests could not see it and a screenshot could. */
.sqempty{padding:28px 16px}
.sqempty .why{max-width:34em;text-align:center;line-height:1.5;color:var(--dim)}
.sqempty .sqline{flex:none;text-align:center}

/* ═══ THE TEACHING LINE ════════════════════════════════════════════════════════════════════
   ⚖ It renders the canonical token string and it MUST NOT be deleted as debug chrome — it is
   the second half of "the box is the truth" (docs/DECISIONS.md): a member taps `video`, watches
   `is:video` appear, and has learned the grammar without a tutorial. It collapses to nothing
   when empty, because a bare feed is a third of all sessions and owes no height to an empty
   string. */
.sqline{font-size:9px;line-height:1.3;color:var(--accent-deep);word-break:break-all;
  margin:3px 0 0;flex:1;min-width:0}
.sqline:empty{display:none}

/* ═══ THE LINE'S OWN ROW — clear and ☆ act on the STRING, not on the row below ═════════════
   ⚖ MOVED OFF `.sqctrl` ON 2026-08-21 (forage-xb6.5) AND IT IS NOT A PLACE TO PUT OVERFLOW.
   `clear` deletes exactly this string and ☆ keeps exactly this string, so they belong against
   the string; `narrow`, `sort` and `time` below RESHAPE the query, which is a different job.
   ⚑ The three of them run on ONE predicate — `canonOf(q)` is empty ⟺ the line is empty ⟺ both
   controls are hidden — so the whole row is `[hidden]` on a bare feed and the front door pays
   NOTHING for it, measured: `#sqmeta` has no client rects at all on `/`. That is what made the
   move affordable — it costs 12px (15 → 27) only while you are actually searching, and it gave
   the control row back ~105px, which is what let a fourth control onto a row that was already
   over its own width in five of eight measured states.
   ⚑ AND IT OWES THE ROW ABOVE IT A GAP, WHICH IT WAS NOT PAYING (forage-h5v, 2026-08-22, found
   by HIT-TESTING the search button rather than by looking at it). The ☆ sits at the END of this
   row, which puts it in a PERFECT 44px COLUMN under the submit button — measured at 390, star
   x=332..376 against button x=332..376, 44px of overlap, at every width and in every state that
   has a query. Its centred 44px halo therefore reached **8.6px UP INTO THE SEARCH BUTTON'S OWN
   DRAWN BOX**, and `elementFromPoint` at the button's bottom-right interior pixel answered
   `sqstar`: on the live tree, a tap on the bottom fifth of the search button SAVED THE SEARCH
   instead of running it. ⚠ It was invisible to every gate and to the eye — a halo draws nothing
   — and it is the failure this sheet's own header warns about in the `.sq-opts` paragraph and
   that the drawer's section headers were re-shaped to avoid.
   ⚖ THE NUMBER IS ARITHMETIC, NOT TASTE, and it is this file's standing formula: a row of 44px
   targets under another needs (44 − drawn height) ÷ 2 of clearance from each side. The star
   draws 22.1px so it hangs 10.95px up; the button draws 40.8px so it hangs 1.6px down; 10.95 +
   1.6 = 12.55, and 13px is that rounded up. ⛔ Do not tune it down to reclaim height — it is
   the one thing standing between a thumb and the wrong verb — and ⛔ do not spend it as a
   `min-height` on the star instead: that grows a control a member can see, which is the closed
   road in docs/DECISIONS.md "The floor may not change what a member can see".
   ⚠ A BARE FEED STILL PAYS NOTHING: the row is `[hidden]`, and a hidden element's margin does
   not draw. */
/* ⚖ THE META ROW JOINS THE CONTROL ROW (owner 2026-08-23: *"the bottom bar is jumbled with
   buttons in bad places"*). `clear` and ☆ used to sit beside the teaching line, which was the
   right home while that line was always drawn — it is the string they delete and keep. Now the
   line prints only when the box disagrees with the server, so on an ordinary search those two
   buttons were left stranded on a row of their own above the controls: two rows of chrome doing
   one job, which is the jumble.
   ⚠ NEGATIVE MARGIN AND `float`-FREE: the row is pulled down onto the control row's line and
   pushed to its RIGHT end, so the reading order stays "what the query is" then "what to do with
   it" — and when the teaching line DOES appear it takes the full width above, unchanged, because
   it is `flex:1` and wraps the buttons after it. */
.sqmeta{display:flex;align-items:center;gap:5px;min-width:0;margin-top:13px}
/* 🐛 THE `display:` vs `[hidden]` TRAP, FIFTH TIME IN THIS SHEET. `display:flex` beats the UA's
   `[hidden]{display:none}`, so without this the empty row would draw its full height — and its
   height is exactly what a bare feed must not pay. */
.sqmeta[hidden]{display:none}
/* ⚠ THE STAR NEEDS REAL WIDTH, not an overhang: the overhang shape spans a control's OWN width
   and a bare ☆ is ~24px. It sits at the END of the row, so a horizontal halo would have nothing
   to its right to steal taps from — but `clear` is on its LEFT, so it takes an honest min-width
   exactly as it did beside `saved`. */
.sqmeta .star{flex:none;background:none;border:1px solid var(--hair);cursor:pointer;position:relative;
  color:var(--dim);font:inherit;font-size:13px;letter-spacing:0;background:var(--sunk);
  min-width:var(--tap-min);display:inline-flex;align-items:center;justify-content:center}
.sqmeta .star.on{color:var(--accent)}
/* 🐛 the same partner rule, and the reason each one in this sheet says so out loud. */
.sqmeta .star[hidden]{display:none}

/* ═══ NARROW + THE QUICK SORT CHIP ═════════════════════════════════════════════════════════
   ⚖ THESE SIT DIRECTLY UNDER THE BOX, NOT ON A BOTTOM HANDLE (owner ruling 2026-08-20). They
   modify the query so they belong beside it — and, load-bearing, A BOTTOM-PINNED CONTROL IS
   COVERED BY THE SOFT KEYBOARD at exactly the moment someone is typing a search. */
/* 🐛 EVERY CONTROL HERE REFUSES TO WRAP ITS OWN LABEL, AND THAT IS A REPAIR, NOT A PRECAUTION
   (forage-xb6.5). Photographed at 360x844 with `sort:dimensions` active: the row was exactly
   full, so `saved 0` broke across two lines inside its own 68px box and stood 49px tall beside
   26px chips. ⚠ Every number was green — it was not clipped and it was not past the edge; a
   squeezed control WRAPS before it clips, so "does the label fit its box" (`scrollWidth`) is a
   different question from "is the box on the screen" and the gate now asks both.
   ⚖ AND THE ROW WRAPS RATHER THAN CLIPS, at the 18px row gap the 44px floor demands (44 minus
   the drawn height — 27px since forage-401, so 17px is the minimum and 18 is what is spent —
   so two rows of overhang cannot overlap; the arithmetic is in this file's header).
   ⛔ THIS IS A DEGRADATION PATH AND NOT A LAYOUT: every state measured at 360
   and 390 fits on ONE line with 8-94px to spare, and `searchbox.js` §20 fails if that stops
   being true. It exists because the alternative failure is the one the owner photographed —
   a control drawn off the right edge of his phone. */
/* ⚖ align-items:STRETCH IS THE "ONE HEIGHT" RULE, AND IT IS NOT A TIDY-UP (forage-401, owner
   2026-08-22: *"we have different sizes of boxes and such. needs a clean up."*). It was
   `center`, and four chips drawn at FOUR different heights — measured on the shipped tree at
   390/360/1280: narrow 29, sort 26.7 (30.6 once the direction arrow appears), when 27.5,
   saved 29. Nothing was clipped, nothing was off the screen and 229 gesture checks were green;
   the row simply had no common line and the owner saw it in one glance.
   ⚠ STRETCH RATHER THAN A `min-height`, DELIBERATELY. A blind minimum on this surface has made
   things worse twice — it changes what a member SEES, which "The floor may not change what a
   member can see" forbids, and a centred 44px box once overlapped its neighbour. Stretch pins
   nothing: it says the boxes are EQUAL and lets the tallest CONTENT decide what equal is, so
   the row cannot drift out of step again when a font or a token changes.
   ⚠ ITS PARTNER IS `line-height:1` ON EVERY GLYPH IN THE ROW below. Without that the count
   chips are sized by the UA's `normal` line box, which is where three of the four heights came
   from. ⛔ Do not restore `align-items:center` here to "centre the labels" — each chip centres
   its OWN content inside the height it is given, which is where centring belongs. */
/* ⚠ 2px OF THAT 5px IS A MARGIN NOW, AND IT COSTS THE PAGE NOTHING (forage-h5v). `.sqctrl` is
   `position:relative` (`.sqpop` hangs off it), so it PAINTS OVER anything under it — including
   the submit button's own 1.6px `::after` overhang, which on a bare feed lands exactly in this
   row's first two pixels. Moving 2 of the 5 from padding to margin puts those pixels outside
   `.sqctrl`'s box: the chips do not move, the row does not move, `anchorPop()` measures the same
   offsets, and the search button gets its floor back. Hit-tested, not read off the CSS. */
/* ⛔ ONE LINE, NEVER WRAPPED (owner ruling 2026-08-23: *"this whole line needs to be a single
   line and never wrap"*). It was `flex-wrap:wrap`, so on a narrow phone the last chip dropped to
   a second row — which broke the row's own laws every time (one height, one uniform gap) and
   read as two rows of chrome doing one job.
   ⚖ IT SCROLLS INSTEAD, exactly as `.sqbox` above it does with its pills: nothing is unreachable,
   the shape stays one line at every width, and the surface already teaches that gesture on the
   element directly above. The alternative — shrinking the two SETTING chips — costs the caption
   that says which of them is the clock, which is defect 3 on this surface and was answered by
   adding that caption. ⛔ Do not re-add `flex-wrap`. */
.sqctrl{display:flex;align-items:stretch;flex-wrap:nowrap;gap:4px;margin-top:2px;padding:3px 0 0;
  position:relative}
/* ⚠ THE SCROLLER IS A CHILD, NOT `.sqctrl` ITSELF, and one attempt the other way is why that is
   written down. `overflow-x:auto` makes an element a scrollport on BOTH axes, and the dropdown
   panels are absolutely positioned INSIDE `.sqctrl` (anchorPop appends them there and measures
   `offsetLeft` against it) — so putting the overflow on the host clipped every menu shut.
   `.sqctrl` stays the positioning host with no overflow; the chips scroll in here. */
.sqctrl{overflow:visible}
.sqrow{display:flex;align-items:stretch;flex-wrap:nowrap;gap:4px;min-width:0;flex:1;
  overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
.sqrow::-webkit-scrollbar{display:none}
.sqrow>*{flex:none}
/* ⚰ THERE IS NO SEPARATOR ON THIS ROW, AND `.sqctrl .sp` IS DELETED (forage-h5v, 2026-08-22).
   ⚑ OWNER, WITH A DESKTOP SCREENSHOT, ONE HOUR AFTER forage-401 SHIPPED: *"why is the saved
   button in the middle of nowhere"*. The separator was `flex:1` first — 902px of nothing to the
   RIGHT of the three query controls on a 1280 desktop — and forage-401 replaced it with a step
   capped at 16px, which moved the void from the left of `saved` to the right of it and left the
   chip aligned to nothing at all. Both shapes were read as an accident, by the same person, at
   the same screen, within an hour.
   ⚖ AND THERE IS NO REAL EDGE TO ALIGN IT TO — that was MEASURED before it was ruled out, not
   argued. The only right edges this column has are the search button's / `.resultbar`'s (one
   and the same, 1226px at 1280) and the pill box's (1174px). Right-aligning `saved` to either
   draws a gap of 86px at 360, 116px at 390 and 926px at 1280 — one rule, three layouts, which
   is precisely the artefact forage-401 removed, restored facing the other way. Aligning to the
   BOX's edge is worse still: it leaves a 52px stub of nothing under the button.
   ⚖ SO `saved` IS THE FOURTH CHIP, ON THE ROW'S OWN 4px GAP. The signal the separator was
   paying for — *"this is the one control here that does not reshape the query"* — is already
   carried by the two SHAPES (forage-401), and `narrow` is a COUNT that sits flush against a
   SETTING with no gap at all. A gap is a third grouping axis stacked on two that already work.
   ⚖ WHAT SURVIVES IS THE PLACEMENT: `saved` is LAST. That is the whole of what forage-876
   ruled; "past a separator" was this file's own gloss on it, twice, and both times wrong.
   ⚑ AND THE TRAILING SPACE IS NOT A GAP — it is the end of a left-aligned row, the same as
   `.qhelp`'s tab row and `.resultbar` directly beneath it, which also stop early at 1280 and
   which nobody has ever read as adrift.
   ⛔ DO NOT REINTRODUCE A SPACER HERE in any spelling. `flex:1` is 902px, `flex:0 1 20px` WRAPS
   at its basis (forage-401 measured `saved` on a second line at 360), and a capped `flex:1 1 0`
   is the 16px that produced the complaint this comment exists to answer. */
.sq-narrow,.sqsort button,.sqsaved button{white-space:nowrap}

/* ═══ TWO KINDS OF CONTROL, TWO SHAPES (forage-401) ════════════════════════════════════════
   ⚖ THE ROW HOLDS EXACTLY TWO KINDS OF THING AND THEY SHOULD LOOK LIKE TWO KINDS:
     · `.sqset` — A SETTING AND ITS CURRENT VALUE. The ordering and the timeframe are the SAME
       object twice: *"this control, currently set to that."* They were drawn as a bare accented
       word and as a captioned two-line chip, which is why nothing on the row said they were
       siblings. Caption over value, both of them.
     · `.sqnum` — A COUNT. `narrow N` and `saved N` are a label beside a number, on one line.
   ⛔ THE COUNTS DO NOT BECOME TWO-LINE TO MATCH. Vertical space is the scarce thing on a phone
   and there is nothing to caption — the label already IS the noun. The heights agree because
   the row stretches, not because every chip grew a caption.
   ⚠ AND THE CAPTION IS FREE IN THE ONE DIMENSION THAT IS SCARCE. `sort` and `when` are both
   NARROWER than every value they label (`dimensions` is the widest in the registry), so the
   chip's width is unchanged; and the row was already as tall as the two-line timeframe, so the
   sort's new caption costs the page no height either. That is the whole reason this shape was
   affordable at 360px, where the widest reachable state has 7.6px of slack. */
.sqset{display:inline-flex;flex-direction:column;align-items:flex-start;justify-content:center;
  gap:2px;position:relative;cursor:pointer;border:1px solid var(--hair);background:var(--sunk);
  font:inherit;padding:3px 7px;white-space:nowrap;text-align:left}
.sqset .k{font-size:7.5px;line-height:1;letter-spacing:calc(.16em * var(--track));
  text-transform:var(--caps);color:var(--faint)}
.sqset .v{font-size:9.5px;line-height:1;letter-spacing:calc(.09em * var(--track));
  text-transform:var(--caps);color:var(--ink)}
/* ⚖ THE VALUE LIGHTS WHEN IT IS NOT THE DEFAULT, and that is ONE state rule for both settings
   rather than a colour each. `shuffle` and `all` are what you get for asking nothing, so they
   are quiet; `votes ↓` and `1w` are choices you made, so they are the accent. ⚠ The sort used
   to be accented ALWAYS, which spent the loudest colour on the row saying "nothing is
   happening" — the same argument `.sq-narrow.armed` already makes about a zero count. */
/* ⚠ TWO SELECTORS BECAUSE THE SORT'S BORDER IS ON THE GROUP, NOT ON THE CHIP. `.sqsort` wraps
   the name AND the direction arrow — two targets inside one drawn box — so the `on` class goes
   on the group there and on the chip itself for the timeframe, which has no second target. */
.sqset.on,.sqsort.on{border-color:var(--accent-deep)}
.sqset.on .v,.sqsort.on .v{color:var(--accent)}
.sqnum{display:inline-flex;align-items:center;gap:5px;position:relative;cursor:pointer;
  border:1px solid var(--hair);background:var(--sunk);color:var(--dim);font:inherit;
  font-size:9.5px;line-height:1;letter-spacing:calc(.12em * var(--track));
  text-transform:var(--caps);padding:5px 6px}
.sqnum .n{color:var(--accent);font-size:11px;line-height:1;font-variant-numeric:tabular-nums}
/* ⛔ NO `font:` SHORTHAND ON ANY RULE BELOW THIS ONE THAT COULD REACH THESE CHIPS, and this is a
   bug that shipped inside this ticket and was caught by measuring. `.sqsaved button{font:inherit}`
   is TWO classes to `.sqnum`'s one, so the shorthand won and reset the font-size AND the
   line-height the whole one-height rule depends on: `saved` drew 84px wide and 33.7px tall, the
   row stretched every other chip up to match it, and at 360 the row wrapped. ⚠ A `<button>` does
   need `font:inherit` — the UA gives it Arial 13.3px — but it belongs on the SHAPE class, or on a
   button that wears neither shape (`.sqsort .dir`), and nowhere else. */
/* ⚰ `.sqctrl .res` WAS HERE AND IS DELETED (forage-l1e, 2026-08-21). It styled a result-count
   slot for this row — including a `b` for the number — that NOTHING HAS EVER EMITTED. It was
   dormant markup for a SECOND HOME for the one number this surface was, on the same day, found
   to be printing twice; leaving it styled is how the next person builds the duplicate again.
   ⚖ THE COUNT HAS ONE HOME: `.resultbar`, directly above the grid it counts. page-index.js
   says so at the line that draws it, and that is the whole reason that bar survived phase 3. */

/* ═══ CLEAR — ONE TAP BACK TO THE BARE FEED (forage-l1e) ══════════════════════════════════
   This is the `×` the deleted `.xpc-pill` carried, and the one capability it had that the box
   did not: without it the only way to empty a four-pill query is four taps. It wears the same
   chip shape as `.sq-narrow`, quiet rather than accented — it REMOVES, so it must never
   out-shout the controls that refine.
   ⚠ IT LIVES IN `.sqmeta` NOW, not in `.sqctrl` (forage-xb6.5) — see that block above for why
   the string's own row is the right home and not a dumping ground. */
.sq-clear{display:inline-flex;align-items:center;position:relative;cursor:pointer;
  border:1px solid var(--hair);background:var(--sunk);color:var(--dim);font:inherit;
  font-size:9.5px;letter-spacing:calc(.12em * var(--track));text-transform:var(--caps);padding:5px 8px}
.sq-clear:hover{color:var(--accent);border-color:var(--hair-hi)}
/* 🐛 THE `display:` vs `[hidden]` TRAP, AND THIS FILE HAS NOW PAID IT FOUR TIMES — `.sqsugg`,
   `.sqsort .dir`, `.sqsaved .star`, and this. `display:inline-flex` above beats the UA's
   `[hidden]{display:none}`, so without this partner the control would draw on a bare feed with
   nothing to clear. ⚠ A test that reads `.hidden` (the property) passes either way; only the
   drawn box can tell you. */
.sq-clear[hidden]{display:none}
/* ⚖ WHAT IS LEFT ON EACH CONTROL IS ITS TIER, NEVER ITS GEOMETRY (forage-401). The shape comes
   from `.sqnum` / `.sqset` above and nothing below may restate a size, a padding or a font —
   that duplication is exactly how four chips came to be four heights. `narrow` is the loud one
   of the two counts (it is the way into the drawer), `saved` the quiet one. */
.sq-narrow{border-color:var(--hair-hi);color:var(--ink)}
.sq-narrow.armed{border-color:var(--accent-deep);color:var(--accent)}
/* THE SORT IS A `.sqset` WITH A SECOND TARGET BOLTED TO IT. The group draws the border and the
   background so the name and the arrow read as one control; the name inside gives up its own
   border rather than drawing a second one inside the first. */
.sqsort{display:inline-flex;border:1px solid var(--hair);background:var(--sunk)}
.sqsort button{cursor:pointer;position:relative}
.sqsort .name{border:0;background:none}
/* ⚠ THE ARROW IS THE ONE CONTROL HERE THAT NEEDS REAL WIDTH, not an overhang. The overhang shape
   spans a control's OWN width, and a bare `↓` is ~26px — so it would still be a 26px target. A
   horizontal halo is not the answer either: it would overhang the sort NAME beside it and steal
   its taps. It takes an honest min-width instead. Costs the chip 18px and no height at all. */
/* ⚠ `line-height:1` HERE IS PART OF THE ONE-HEIGHT RULE (forage-401). At the UA's `normal` line
   box a 12px arrow drew a 30.6px group beside a 26.7px one — the sort chip CHANGED HEIGHT
   depending on whether the sort it was showing could be inverted, which is a control that
   resizes itself for a reason no member could ever guess. */
.sqsort .dir{border-left:1px solid var(--hair);color:var(--ink);font:inherit;font-size:12px;line-height:1;
  letter-spacing:0;padding:5px 7px;background:none;border-top:0;border-right:0;border-bottom:0;
  min-width:var(--tap-min);display:inline-flex;align-items:center;justify-content:center}
/* 🐛 THE SAME `display:` vs `[hidden]` TRAP AS `.sqsugg` ABOVE, AND IT SHIPPED A LIE.
   A sort that is not `invertible` cannot spend `dir:` — the parser answers 400 — so the arrow
   is HIDDEN for it. `.hidden = true` sets the attribute, and `display:inline-flex` here beat the
   UA's `[hidden]{display:none}`, so `shuffle` and `recent` kept drawing a live-looking arrow
   whose only possible answer was an error. ⚠ THE TEST THAT "COVERED" THIS READ `.hidden` — the
   PROPERTY — and passed all the way through; it was caught by looking at the screenshot. That is
   the whole content of the 2026-08-19 ruling, arriving again. */
.sqsort .dir[hidden]{display:none}

/* ═══ TIME — THE SECOND PEER DROPDOWN (forage-xb6.5, owner directive 2026-08-21) ═══════════
   ⚖ ORDERING AND TIMEFRAME ARE TWO QUESTIONS, SO THEY ARE TWO CONTROLS: `[ sort ▾ ] [ time ▾ ]`,
   side by side under the box. They used to be one panel — the sort menu carried a WHEN strip
   at its foot — and answering "how far back" meant scrolling past the whole of "how do I order
   it". The owner photographed that and called it *"super clunky"*.
   ⚖ IT DRAWS THE WINDOW TOKEN, NOT A SENTENCE — `1w`, `3m`, `all`. That is not an abbreviation
   for want of room, it is the strongest possible answer to defect 3 on this surface: this is
   the one control here that can show, at chip scale, the exact thing it writes into the query
   (`since:1w`). The full words and every window's COUNT are in the dropdown, where there is
   room for both. ⚠ A LONGER LABEL DOES NOT FIT AND THAT IS MEASURED: spelling `all time` on the
   chip puts the 360px row back over its own width in the widest sort's state, which is the whole
   defect this ticket is fixing.
   🐛 THE CAPTION WAS ADDED AFTER LOOKING AT THE SCREENSHOT, with every number already green. The
   chip read a bare `ALL` between `VOTES ↓` and `SAVED 0` and nothing on the row said which of
   them was the clock — a control the member cannot IDENTIFY is the same defect as one they
   cannot see. It is two lines because `when` over `1w` costs 6px of height and NO width at all:
   the caption is narrower than the value it labels.
   ⚑ AND THAT ARGUMENT WAS ALWAYS TRUE OF THE SORT TOO, WHICH IS WHAT forage-401 FIXED: the
   caption that made the clock identifiable was the RIGHT shape and it was only ever given to
   one of the two settings, so the row read as three kinds of control instead of two. This chip
   has no rules of its own any more — it IS `.sqset`, and so is the sort. ⛔ Do not give it back
   a font size or a padding here; the two must be able to disagree only about their value. */

/* ═══ SAVED SEARCHES — THE LIST (forage-876) ══════════════════════════════════════════════
   ⚖ It sits LAST in the control row, because it is the only control there that does not
   reshape anything — narrow, sort and time modify the query, this one opens the KEPT ones.
   ⚠ Its other half, the ☆, moved to `.sqmeta` on 2026-08-21: the star acts on the canonical
   STRING, so it belongs on the string.
   ⚑ THIS PARAGRAPH HAS BEEN WRONG TWICE ABOUT THE SAME 40 PIXELS. It said "at the RIGHT END",
   which drew 902px of nothing on a desktop; forage-401 said "one deliberate step past the
   separator", which drew a 16px gap and a void to the right of the chip; the owner read BOTH as
   accidental within an hour of each other. LAST is the placement, and last is all it is —
   `saved` is the fourth chip on the row's own uniform gap, with no separator at all. The
   measurements that closed the other two roads are at the deleted `.sqctrl .sp` above.
   ⚠ THE WRAPPER NO LONGER DRAWS THE BOX. It held two buttons once; it holds one, and a bordered
   wrapper around a bordered `.sqnum` is two boxes drawn on top of each other. */
.sqsaved{display:inline-flex}
.sqsaved button{cursor:pointer;position:relative}
.sqsaved .list.armed{color:var(--ink)}
/* THE LIST ITSELF rides the `.sqpop` shape below — one menu component, two menus. */
.sqpop.right{left:auto;right:0;max-width:min(92vw,320px)}
.sqpop .empty{font-size:10px;color:var(--faint);padding:9px 12px;line-height:1.5}
.sqpop .row{display:flex;align-items:stretch;border-bottom:1px solid var(--hair)}
.sqpop .row:last-child{border-bottom:0}
.sqpop .row .go{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;align-items:flex-start;
  justify-content:center;text-align:left;border-bottom:0;padding:5px 10px;min-height:var(--tap-min)}
.sqpop .row .go .nm{font-size:10.5px;color:var(--ink);text-transform:none;letter-spacing:0}
/* the QUERY, and it is the label — a saved search is the token string, so the string is what
   is drawn. It wraps rather than truncating: a query you cannot read is not a bookmark. */
.sqpop .row .go .tk{font-size:8px;line-height:1.35;color:var(--accent-deep);text-transform:none;
  letter-spacing:0;word-break:break-all;white-space:normal}
.sqpop .row .x{flex:none;border-bottom:0;border-left:1px solid var(--hair);color:var(--faint);
  font-size:11px;min-width:var(--tap-min);display:inline-flex;align-items:center;justify-content:center}
.sqpop .row .x:hover,.sqpop .row .x:focus-visible{color:var(--bad)}

/* ═══ WHEN — THE TIME DROPDOWN'S CONTENTS (forage-lsp, re-shaped by forage-xb6.5) ══════════
   ⚰ IT WAS A SIDEWAYS-SCROLLING STRIP UNTIL 2026-08-21 AND THAT SHAPE IS THE BUG. Eleven stops
   in one non-wrapping row is ~470px of chips, so on a 390px phone eight of them were off-screen
   and the visible edge was cut mid-word — the member could not see that there was anything past
   `3 da…`. ⚑ IT PASSED ITS GATE THE WHOLE TIME: the assertion was *"the strip scrolls"*, and a
   strip showing one option with ten pushed off the end satisfies `scrollWidth > clientWidth`
   perfectly. A control's options being REACHABLE is a geometry question and it has to be asked
   as one — the gate asks it that way now (`searchbox.js` §16).
   ⚖ SO IT IS A GRID: every window on screen at once, nothing clipped, nothing to scroll. Cells
   tile at --tap-min, which is the `.ac div` argument — where rows AND columns tile, the 44px
   floor costs the layout nothing and needs no gap arithmetic, so this does NOT pay the wrapping
   canyon that stranded `shuffle`.
   ⚖ AND EVERY CELL STILL CARRIES ITS COUNT. `all time 3,068` next to `1 day 505` is the point
   of the control — you see what you would get before you tap — and it is defect 4 on this
   surface to draw one without. The grid is what let them all stay: a strip had to choose
   between showing eleven counts and showing eleven options, and it silently chose neither.
   ⚠ 84px IS THE FLOOR OF A CELL, not its width. `auto-fill` picks the column count from the
   room there actually is — three in the drawer and in the menu on a phone, four on a desktop —
   so the same builder serves both hosts without either knowing the other's width. */
.sq-when{display:flex;flex-direction:column;gap:5px;min-width:0;padding:8px 10px}
.sq-grp>.sq-when{padding:0}
.sq-when .h{font-size:8.5px;letter-spacing:calc(.18em * var(--track));text-transform:var(--caps);
  color:var(--faint)}
.sq-when .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:3px;
  min-width:0}
.sq-when .w{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:2px;
  min-height:var(--tap-min);position:relative;cursor:pointer;border:1px solid var(--hair);
  background:var(--sunk);color:var(--ink);font:inherit;padding:4px 7px;text-align:left;
  white-space:nowrap;overflow:hidden}
.sq-when .w .nm{font-size:10px;line-height:1}
.sq-when .w .ct{font-size:8px;line-height:1;color:var(--faint);font-variant-numeric:tabular-nums}
.sq-when .w.on{border-color:var(--accent-deep);background:var(--raised)}
.sq-when .w.on .nm{color:var(--accent)}

/* THE QUICK SORT MENU — the six orders worth reaching without opening the drawer. It hangs off
   `.sqctrl` (which is `position:relative`) rather than off the chip, so it cannot be clipped by
   the chip's own border box, and it is the same shape as app.css's `.sortmenu`.
   ⚠ ROWS TILE AT --tap-min, exactly like `.sqsugg button`: where rows tile, the 44px floor costs
   the layout nothing and needs no gap arithmetic at all. */
/* 🐛 THE MAX-WIDTH IS LOAD-BEARING AND IT WAS FOUND BY LOOKING (forage-lsp). `.sqpop` had a
   min-width and no ceiling, so the moment the WHEN strip's non-shrinking chips landed inside it
   the MENU grew to their total width and ran off the right edge of a 390px screen — the strip
   never scrolled at all, because a scroll container cannot scroll inside a parent that is happy
   to be 700px wide. The `min-width:0` partners below are the other half: a flex child's default
   `min-width:auto` refuses to shrink below its content, which silently defeats `overflow-x`. */
.sqpop{position:absolute;left:0;top:calc(100% + 2px);z-index:var(--z-menu);min-width:132px;
  max-width:min(92vw,340px);
  display:flex;flex-direction:column;
  border:1px solid var(--hair-hi);background:var(--raised);box-shadow:0 8px 22px rgba(0,0,0,.5)}
.sqpop>*{min-width:0}
/* ⚖ A DROPDOWN HANGS UNDER THE CONTROL THAT OPENED IT. `left` is written by
   page-index-search.js's `anchorPop()` from the chip's own offset inside `.sqctrl`, then
   CLAMPED to the row — a menu is anchored to its control OR it is on the screen, and when the
   two disagree the screen wins. ⛔ Never let it hang off the edge to stay under its chip: that
   is the 700px menu this sheet already paid for once, in a different disguise. */
.sqpop.time{min-width:min(92vw,300px)}
/* 🐛 `width:100%` USED TO LIVE ON `.sqpop button` AND IT BROKE BOTH THINGS THAT LATER MOVED
   INTO THIS MENU — found by LOOKING at a 390x844 screenshot, with every assertion green.
   The sort keys are a stack of full-width rows and want it; a WHEN chip in a sideways strip and
   the ✕ on a saved row are flex items in a ROW, and a 100% width there means one child claims
   the whole menu. `all time` filled the strip and pushed the other ten stops out of sight
   (which still "scrolls", so the scrollWidth check passed), and the ✕ took 340px while the
   query it belonged to was squeezed to one character per line.
   ⚖ SO THE FULL-WIDTH RULE IS SCOPED TO THE STACK IT DESCRIBES (`.keys`), and what is left on
   `.sqpop button` is only what every button in this menu really shares. */
.sqpop button{background:none;border:0;cursor:pointer;color:var(--dim);font:inherit;
  font-size:10.5px;letter-spacing:calc(.1em * var(--track));text-transform:var(--caps)}
.sqpop .keys{display:flex;flex-direction:column}
.sqpop .keys button{display:flex;align-items:center;width:100%;text-align:left;
  border-bottom:1px solid var(--hair);padding:0 12px;min-height:var(--tap-min)}
.sqpop .keys button:last-child{border-bottom:0}
.sqpop .keys button.on{color:var(--accent)}

/* ═══ THE ORDER LADDER ═════════════════════════════════════════════════════════════════════
   ⚖ EVERY `^` BOOST IS A RUNG ABOVE THE NUMERIC SORT, in the order it was added, and the sort
   is the last rung. That is not a metaphor: search.js compiles a boost as an ORDER BY in FRONT
   of the sort's own, so the ladder draws the shape the SQL already has — which is the whole
   content of "filter and sort are two strengths of one gesture".
   ⚠ RUNGS TILE, so again no gap arithmetic: each row IS 44px of target with nothing wasted
   between them (app.css's `.ac div` argument — tiling is not crowding). */
.sq-rungs{display:flex;flex-direction:column;gap:2px}
.sq-rung{display:flex;align-items:center;gap:7px;min-height:var(--tap-min);
  border:1px solid var(--hair);background:var(--sunk);padding:5px 7px;font-size:10px}
.sq-rung .ix{width:9px;flex:none;font-size:8px;color:var(--faint);font-variant-numeric:tabular-nums}
.sq-rung .nm{color:var(--ink)}
.sq-rung .tk{margin-left:auto;font-size:7.5px;color:var(--accent-deep)}
.sq-rung .dir{background:none;border:0;font:inherit;cursor:pointer;color:var(--accent);
  font-size:12px;padding:2px 8px;position:relative;min-width:var(--tap-min)}
/* `.rm` is the × on the SECOND RUNG (forage-xb6.4) — the same modifier shape as the direction
   arrow, one rung down, the same real min-width for the same reason, and the same coarse
   overhang below. ⚠ ITS OWN RULE, NOT A COMMA ON `.dir`'S: mobiletaps.test.js judges a rule by
   its whole selector text, and `.sq-rung .dir,.sq-rung .rm` read as a control with no escape. */
.sq-rung .rm{background:none;border:0;font:inherit;cursor:pointer;color:var(--faint);
  font-size:12px;padding:2px 8px;position:relative;min-width:var(--tap-min)}
/* the caption over the tiebreak picker — the second rung's own row of options, under the ladder */
.sq-ties{font-size:8.5px;color:var(--faint);letter-spacing:calc(.18em * var(--track));
  text-transform:var(--caps);margin-top:2px}

/* ═══ SUGGESTIONS — where the separation becomes visible ═══════════════════════════════════
   ⚖ EVERY ROW CARRIES ITS COUNT. Typing `dog` offers the TAG (19) beside the TEXT scopes (313
   described, 246 anywhere) — wildly different numbers, and that difference IS the argument for
   separating them. A suggestion list without counts is the same defect as a facet without them,
   and with 368 of 674 tags used exactly once the count is all that separates a tag from noise.
   ⚠ Rows TILE at --tap-min, so the floor costs this list nothing — the `.ac div` argument from
   app.css: tiling is not crowding. */
.sqsugg{position:absolute;left:0;right:0;top:calc(100% + 2px);z-index:var(--z-menu);
  display:flex;flex-direction:column;max-height:230px;overflow-y:auto;
  border:1px solid var(--hair-hi);background:var(--raised);box-shadow:0 10px 26px rgba(0,0,0,.55)}
/* 🐛 `display:flex` BEATS THE UA'S `[hidden]{display:none}`, so the shut list still drew its
   1px border — a stray amber-grey rule under the box on every screen with no suggestions up.
   Photographed at 390x844 before it was believed. Any `display:` on a hideable element needs
   this partner rule — `.sqsort .dir`, `.sqsaved .star` and `.sq-clear` each spend a line on it
   below, which is four in one sheet and the reason each one says so out loud. */
.sqsugg[hidden]{display:none}
.sqsugg button{display:flex;align-items:center;gap:8px;width:100%;text-align:left;cursor:pointer;
  background:none;border:0;border-bottom:1px solid var(--hair);color:var(--ink);
  font:inherit;font-size:11px;padding:0 9px;min-height:var(--tap-min)}
.sqsugg button:last-child{border-bottom:0}
.sqsugg button:hover,.sqsugg button:focus-visible{background:var(--sunk)}
/* ⚖ THE KIND IS A MARK, AND THE MARK IS WELDED TO THE VALUE (F1 CORRECTED 2026-09-03). The
   owner, on the first build: *"why does the text has the # too when the rest don't have their
   icons."* It painted a `.km` mark COLUMN inside `.k` and, on tag rows only, a `#` on the value
   as well — the kind stated twice on one row kind and once on every other. ⛔ `.km` IS GONE.
   `.k` is the caption WORD alone, 8px --dim (spec §1.1), sized to its content so it never
   clips against `.v` (F2), with the button's 8px gap before it. */
.sqsugg .k{flex:none;font-size:8px;
  letter-spacing:calc(.1em * var(--track));text-transform:var(--caps);color:var(--dim)}
/* ONE ROW SHAPE: `KIND [mark]value count`. `.vs` is the mark, at the HEAD OF THE VALUE, for
   every kind — `#` in --accent, the square face then `@` in --bright, scopeMark's ≡/eye/frame/
   speaker in --info — from the same registry the pills and grid tiles draw from. It sits in the
   value's flex row at a 1px gap: WELDED, so `#cat` reads as one word (spec §1.1, §1.3).
   ⚠ 14px min-height, not a font-size alone: the glyph has to read at row height.
   ⚠ AND `.v` STAYS INLINE — an inline-flex head inside it keeps a long phrase WRAPPING the way
   text does; making `.v` a flex row turns the phrase into a flex item that cannot shrink past
   its longest word and pushes the count off the row. */
.sqsugg .vs{display:inline-flex;align-items:center;gap:1px;vertical-align:middle;
  margin-right:1px;min-height:14px;font-size:12px;line-height:1;
  text-transform:none;letter-spacing:normal}
.sqsugg button.k-tag .vs{color:var(--accent);font-weight:700}
.sqsugg button.k-by .vs{color:var(--bright);font-weight:700}
.sqsugg .vs .sci{width:13px;height:13px;color:var(--info)}
.sqsugg .vs .vw-cav{width:14px;height:14px;border-width:1px}
.sqsugg .vs .vw-cav-i{font-size:8px}
.sqsugg .v .fz{color:var(--info)}
.sqsugg .n{margin-left:auto;font-size:9.5px;color:var(--faint);font-variant-numeric:tabular-nums}

/* ═══ THE NARROWING DRAWER ═════════════════════════════════════════════════════════════════
   ⚖ THE DRAWER IS FOR ENUMERABLE THINGS; THE BOX IS FOR OPEN-ENDED ONES. Tags, media types and
   uploaders can be listed with counts, so they are facets and they live here. Text cannot be
   enumerated — there is no list of every possible phrase — so it lives in the box, with its
   SCOPE offered as a suggestion once something has been typed. */
/* ⚖ THE SHEET IS AS TALL AS WHAT IS IN IT (forage-gk0). It used to be `top:32%` — a fixed
   68% of every phone, whatever it held — which was the right call while every section was
   always open and wrong the moment they are not: five shut headers is ~225px, and pinning a
   574px panel over the results to show them was the owner's complaint restated as CSS. It
   still cannot grow past 68%, so a fully-expanded drawer is exactly the sheet it always was. */
/* ⚖ A SHUT DRAWER IS NOT DRAWN AT ALL — `visibility:hidden`, not merely translated out of sight
   (owner report 2026-08-24, *"the narrow tab at the bottom seems to slide up and down"*).
   `translateY(101%)` hides a box by ONE PERCENT OF ITS OWN HEIGHT, and a shut drawer is only its
   47.7px header (the body is empty until `openDrawer()` fills it), so 101% is 48.16px and the
   clearance is 0.48px. MEASURED on the real grid at 390x844: the tab's top edge sits 0.48px
   below the fold. A mobile browser slides its own fixed layer by tens of pixels while it
   animates the URL bar, so half a pixel of hiding margin is not hiding — it is a full-width bar
   with a grip on it, one browser frame away from being on screen, and that is what the owner
   was watching slide.
   ⚠ AND THE SHADOW WAS ALREADY THROUGH THE DOOR. `0 -18px 44px` paints 18 + 44/2 = 40px ABOVE
   the box, i.e. inside the viewport. Photographed with the drawer present and again with it
   removed, same content: the bottom band of every search page was darkened by up to 38%, a
   vignette nobody designed, drawn by a panel that is supposed to be shut.
   ⛔ NOT a bigger translate and NOT a transition tuned until the motion stops — a hidden element
   cannot be revealed by any amount of compositor slide, and that is the only property worth
   relying on here. The visibility flip is DELAYED by the slide-out's own .22s on close so the
   drawer is still painted while it leaves, and is immediate on open. */
.sqdrawer{position:fixed;left:0;right:0;bottom:0;top:auto;max-height:68%;z-index:var(--z-sheet,300);
  display:flex;flex-direction:column;background:var(--panel);border-top:1px solid var(--accent-deep);
  box-shadow:0 -18px 44px rgba(0,0,0,.55);transform:translateY(101%);visibility:hidden;
  transition:transform .22s ease,visibility 0s linear .22s}
.sqdrawer.open{transform:translateY(0);visibility:visible;transition:transform .22s ease,visibility 0s}
@media(prefers-reduced-motion:reduce){.sqdrawer,.sqdrawer.open{transition:none}}
.sqdrawer .top{display:flex;align-items:center;gap:7px;flex:none;cursor:pointer;
  padding:6px 12px;border-bottom:1px solid var(--hair)}
.sqdrawer .grip{width:26px;height:3px;background:var(--hair-hi);flex:none}
.sqdrawer .cnt{font-size:9px;letter-spacing:calc(.12em * var(--track));text-transform:var(--caps);
  color:var(--faint)}
.sqdrawer .cnt b{color:var(--accent);font-size:12.5px;font-weight:400;font-variant-numeric:tabular-nums}
/* ⚠ THE INSET IS SPENT HERE, ON THE SCROLLER, NOT ON THE PANEL (forage-njzy). The sheet's own
   bottom edge IS the screen's bottom edge and that is correct; what was wrong is that its
   CONTENT stopped 12px above it, so the last `.sq-opt` of whatever section you scrolled to was
   drawn inside the ~34px home-indicator strip — with its 44px coarse-pointer halo reaching
   further in, where iOS claims the swipe and Android gesture-nav claims back/home. Every other
   bottom-anchored fixed surface here already spends the inset (.verdictbar, .pwasheet, .vw-bot,
   .lp-sheet); this one had none, which made it an omission and not a house style.
   ⚠ It lands on the SCROLLER's padding because `.sqdrawer` has a PERCENTAGE max-height, not a
   hard one — padding a fixed-SIZE box is the shape mobilesafearea.test.js forbids (.xps-fab). */
.sqdrawer .body{flex:1;overflow-y:auto;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}
/* ⚖ EVERY SECTION COLLAPSES AND IS SHUT BY DEFAULT (forage-gk0, owner directive 2026-08-21:
   *"these need to be drop downs or auto collapsed. they take up a lot of space for something you
   may not always use."*). The header is the whole control — a full-width button, so the target
   is the row rather than a chevron a thumb has to find. ⚠ `.h` IS A <button> NOW: it has to be
   stripped back to the text it used to be (no chrome, no browser font, left-aligned, no padding)
   or the drawer grows five boxes it never had.
   ⚖ A SHUT SECTION STILL NAMES WHAT IT IS DOING — `.sqact` is the criterion it is filtering by, in
   the accent, wearing THE TOKEN. `b` is the list's own caption (`top 14 of 30`) and is drawn only
   when the section is OPEN, because it describes options that are not on screen when it is not. */
.sq-grp{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--hair)}
.sq-grp>.h{display:flex;align-items:center;gap:7px;width:100%;position:relative;cursor:pointer;
  background:none;border:0;padding:0;margin:0;text-align:left;font:inherit;color:var(--faint)}
.sq-grp>.h .nm{font-size:8.5px;letter-spacing:calc(.18em * var(--track));text-transform:var(--caps)}
/* ⚠ `.sqact` AND NOT `.act`, WHICH IS WHAT IT WAS FOR ONE RUN: app-feeds.css owns a global
   `.act` — the verdict bar's bordered action button — and it loads BEFORE this sheet, so the
   summary was drawn as an outlined chip in the middle of a section header. Found by LOOKING at
   a 390x844 screenshot with all 184 checks green. ⚖ A class added to this shared namespace
   needs the `sq` prefix every other name on this surface already carries. */
.sq-grp>.h .sqact{font-size:9.5px;color:var(--accent);letter-spacing:0;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ⚖ `.dflt` IS THE ORDER SECTION SAYING WHAT THE ORDER IS WITHOUT CLAIMING THE MEMBER CHOSE IT.
   There is always an order, so that header always names one; the accent is reserved for a
   choice, or every search would look narrowed by something. */
.sq-grp>.h .sqact.dflt{color:var(--dim)}
.sq-grp>.h b{color:var(--dim);font-weight:400;font-size:9.5px;letter-spacing:0;text-transform:none}
/* the chevron. `margin-left:auto` parks it at the right edge whatever the header says. */
.sq-grp>.h .dis{width:11px;height:11px;flex:none;margin-left:auto;
  transition:transform .14s ease}
.sq-grp.open>.h .dis{transform:rotate(90deg)}
@media(prefers-reduced-motion:reduce){.sq-grp>.h .dis{transition:none}}
.sq-ord{display:flex;flex-direction:column;gap:6px}
/* the per-group search bar. ⚠ 16px again: this is an input on a phone. */
.sq-find{width:100%;background:var(--sunk);border:1px solid var(--hair);color:var(--ink);
  font:inherit;font-size:16px;padding:6px 7px;outline:none}
.sq-find:focus{border-color:var(--accent-deep)}
.sq-find::placeholder{color:var(--faint)}
/* ⚠ ROW 16 / COLUMN 3 — see this file's header. The asymmetry is the 44px arithmetic, not taste. */
.sq-opts{display:flex;flex-wrap:wrap;gap:16px 3px}
.sq-opt{display:flex;flex-direction:column;gap:2px;min-width:66px;position:relative;cursor:pointer;
  border:1px solid var(--hair);background:var(--sunk);color:var(--ink);font:inherit;
  padding:4px 6px;text-align:left}
.sq-opt .r{display:flex;align-items:baseline;gap:5px;width:100%}
.sq-opt .nm{font-size:10.5px;line-height:1}
.sq-opt .ct{margin-left:auto;font-size:8.5px;color:var(--faint);font-variant-numeric:tabular-nums}
.sq-opt .tk{font-size:7.5px;line-height:1;color:var(--accent-deep)}
.sq-opt .sy{font-size:11px;line-height:1;font-weight:700;flex:none}
.sq-opt.on{background:var(--raised)}
.sq-opt.on.is-prefer{border-color:var(--hair-hi)}
.sq-opt.on.is-prefer .sy{color:var(--accent-deep)}
.sq-opt.on.is-only{border-color:var(--accent-deep)}
.sq-opt.on.is-only .nm,.sq-opt.on.is-only .sy{color:var(--accent)}
.sq-opt.on.is-excl{border-color:var(--bad)}
.sq-opt.on.is-excl .nm,.sq-opt.on.is-excl .sy,.sq-opt.on.is-excl .tk{color:var(--bad)}
.sq-opt.zero{opacity:.36}
.sq-none{font-size:10px;color:var(--faint);padding:4px 2px}

/* ⚖ THE ☆ AT THE RIGHT END OF THE CONTROL ROW (owner 2026-08-23). It wears `.sqnum`'s box
   exactly — same padding, same line-height — because the row's law is that every chip on it is
   ONE height, and three pixels fails that census as loudly as seventeen.
   ⚠ A REAL 44px OF WIDTH, NOT A HALO, and searchbox.js asks for it by name: width is free here
   (the laws are about height and gap) while a horizontal overhang would reach across the uniform
   4px gap and steal the tap of the chip beside it. The VERTICAL floor still comes from the
   coarse-pointer block below, on the shape, like every other control. */
.sqctrl .star{flex:none;background:var(--sunk);border:1px solid var(--hair);cursor:pointer;
  color:var(--faint);font-size:11px;line-height:1;padding:5px 6px;position:relative;
  min-width:44px;display:inline-flex;align-items:center;justify-content:center}
.sqctrl .star.on{color:var(--accent);border-color:var(--accent-deep)}
/* 🐛 THE `display:` vs `[hidden]` TRAP, SIXTH TIME IN THIS SHEET AND IT CAUGHT ME TOO. */
.sqctrl .star[hidden]{display:none}

/* ═══ THE 44px FLOOR, APPLIED — COARSE POINTERS ONLY ═══════════════════════════════════════
   ⚖ ONE BLOCK, AT THE END, AND IT MUST STAY A COARSE BLOCK. Two reasons, and the second is why
   this is not merely tidy:
     · the floor exists for THUMBS. A mouse hits a 21px pill perfectly well, and invisible
       halos on a desktop pointer only widen click regions nobody asked to widen.
     · server/tests/mobiletaps.test.js READS THE CASCADE FOR THIS SHAPE and only looks inside
       `@media(pointer:coarse)` blocks. An overhang declared unconditionally is invisible to the
       census, so every control in this file reports as under the floor — which is precisely the
       report that got `.btn` fattened on 26 screens. Declared here, it is both correct and seen.
   ⚠ `left:0;right:0` + a --tap-min HEIGHT, never a width: the box keeps its own width (a
   labelled control already clears 44px) and grows only vertically, because a horizontal halo
   would overhang the control BESIDE it and steal its taps. `.sqsort .dir` is the one exception
   and takes a real min-width instead — see its rule above for why. */
@media(pointer:coarse){
  /* ⚖ THE OVERHANG IS ON THE SHAPE, NOT ON EACH CONTROL (forage-401). It used to name
     `.sq-narrow`, `.sqsort button`, `.sqsaved button` and `.sqtime` one at a time; those four
     are two shapes now, and hanging the floor on `.sqnum` / `.sqset` means the fifth control
     added to this row gets it by wearing a shape rather than by being remembered here.
     ⚠ `.sqsort .dir` IS THE EXCEPTION AND STILL NEEDS ITS OWN LINE — it is the one button on
     the row that wears neither shape, because it is a modifier on the chip beside it rather
     than a control with a label of its own. */
  .sqpill::after,.sqnum::after,.sqset::after,.sqsort .dir::after,.sq-opt::after,
  .sq-rung .dir::after,.sq-rung .rm::after,.sqmeta .star::after,.sqctrl .star::after,.sq-when .w::after,.sq-clear::after{
    content:"";position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
    height:var(--tap-min)}
  /* ⚖ THE SECTION HEADERS TAKE A REAL 44px INSTEAD OF A HALO, and that is the one place in this
     sheet where the halo is the WRONG shape (forage-gk0). Five shut headers STACK directly on
     top of each other — 27px apart — so five 44px halos would overlap and each one would steal
     the taps of the header above it, which is the exact failure `left:0;right:0` was chosen to
     avoid sideways. Headers TILE, and where things tile the floor costs the layout nothing:
     app.css's `.ac div` and this file's `.sqsugg button` are the same argument, and the shut
     section's own vertical padding is given back so 44px is the whole row and not 60. */
  .sq-grp>.h{min-height:var(--tap-min)}
  .sq-grp:not(.open){padding-top:0;padding-bottom:0}
}

/* ── THE SCOPE MARK ON A QUERY PILL (owner ruling 2026-08-23, common-icons.js SCOPE_ICON) ──
   ⚠ THE BOX IS THE CALLER'S, THE PAINT IS THE NODE'S. scopeMark hands back a `.sci` that
   carries its own fill/stroke precisely so it does NOT inherit `svg.navi` and with it the
   masthead's 14px cell. 13px is the size the marks were rasterised and compared at — the
   frame on `ocr` and the ragged last line on `text` are what survive the downscale, and both
   stop resolving below it. */
.sqpill .sci{width:13px;height:13px;flex:none;margin-right:1px}

/* ── A TAG PILL WEARS ITS NAMESPACE (owner ruling 2026-08-23: "the color of the tag box should
   be the color of the tag type") ──
   ⚖ IT REUSES `k-<namespace>`, THE SAME CLASS EVERY OTHER TAG CHIP IN THE ARCHIVE WEARS, so the
   colour comes from app-viewer.css's "LAST WORD ON TAG COLOUR" block and there is no second
   table of hues to drift. That block is also what makes a CONTENT WARNING outrank a namespace —
   a tag named `gore` with kind='media' must not render purple — and reusing the class inherits
   that ordering for free rather than re-deciding it here.
   ⚠ NO COLLISION WITH THE CRITERION KIND, which this element also carries as `k-tag`/`k-text`:
   the two vocabularies share no word (tag/text/by/is/related/raw vs general/character/meta/
   media/humor/nsfw/nsfl), and that is why one prefix can safely carry both.
   ⚠ ONLY THE BORDER IS ADDED HERE, and only when the pill is `is-only`. The text colour is
   already handled above; a preferred or excluded pill keeps the accent or the bad colour,
   because a namespace hue competing with an EXCLUSION would make "not this" read as decoration.
   ⚠ AND ONLY WHEN THE NAMESPACE IS KNOWN: `k-general` is the fallback and deliberately has no
   border rule, so an unrecognised tag looks exactly as it did before. */
.sqpill.k-tag.is-only[class*=" k-"]{border-color:currentColor}
/* ⚠ BOTH GLYPH CLASSES, BECAUSE THE PILL'S `#` MOVED FROM `.sg` TO `.vs` (F19) and this block
   is written TWICE in this sheet (STYLES.txt DUP=2) — fix one and the other stays broken. */
.sqpill.k-tag.is-only .sg,.sqpill.k-tag.is-only .vs{color:inherit}

/* ── THE RIGHT END OF THE CONTROL ROW (owner ruling 2026-08-23): the star is always last, the
   book is to its left, and with no search the book is last because a blank search cannot be
   saved. ──
   ⚠ NO `margin-left:auto` HERE, AND THAT IS THE WHOLE POINT. One attempt at this put the two
   actions after `saved` with an auto margin and produced an 806px gap on a 1280 desktop —
   forage-h5v's own defect, *"why is the saved button in the middle of nowhere"*, rebuilt from
   the other end and caught by searchbox.js the same afternoon. These sit on the row's ONE
   uniform gap like every other chip. */
/* ═══ THE 44px FLOOR, APPLIED — COARSE POINTERS ONLY ═══════════════════════════════════════
   ⚖ ONE BLOCK, AT THE END, AND IT MUST STAY A COARSE BLOCK. Two reasons, and the second is why
   this is not merely tidy:
     · the floor exists for THUMBS. A mouse hits a 21px pill perfectly well, and invisible
       halos on a desktop pointer only widen click regions nobody asked to widen.
     · server/tests/mobiletaps.test.js READS THE CASCADE FOR THIS SHAPE and only looks inside
       `@media(pointer:coarse)` blocks. An overhang declared unconditionally is invisible to the
       census, so every control in this file reports as under the floor — which is precisely the
       report that got `.btn` fattened on 26 screens. Declared here, it is both correct and seen.
   ⚠ `left:0;right:0` + a --tap-min HEIGHT, never a width: the box keeps its own width (a
   labelled control already clears 44px) and grows only vertically, because a horizontal halo
   would overhang the control BESIDE it and steal its taps. `.sqsort .dir` is the one exception
   and takes a real min-width instead — see its rule above for why. */
@media(pointer:coarse){
  /* ⚖ THE OVERHANG IS ON THE SHAPE, NOT ON EACH CONTROL (forage-401). It used to name
     `.sq-narrow`, `.sqsort button`, `.sqsaved button` and `.sqtime` one at a time; those four
     are two shapes now, and hanging the floor on `.sqnum` / `.sqset` means the fifth control
     added to this row gets it by wearing a shape rather than by being remembered here.
     ⚠ `.sqsort .dir` IS THE EXCEPTION AND STILL NEEDS ITS OWN LINE — it is the one button on
     the row that wears neither shape, because it is a modifier on the chip beside it rather
     than a control with a label of its own. */
  .sqpill::after,.sqnum::after,.sqset::after,.sqsort .dir::after,.sq-opt::after,
  .sq-rung .dir::after,.sq-rung .rm::after,.sqmeta .star::after,.sqctrl .star::after,.sq-when .w::after,.sq-clear::after{
    content:"";position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
    height:var(--tap-min)}
  /* ⚖ THE SECTION HEADERS TAKE A REAL 44px INSTEAD OF A HALO, and that is the one place in this
     sheet where the halo is the WRONG shape (forage-gk0). Five shut headers STACK directly on
     top of each other — 27px apart — so five 44px halos would overlap and each one would steal
     the taps of the header above it, which is the exact failure `left:0;right:0` was chosen to
     avoid sideways. Headers TILE, and where things tile the floor costs the layout nothing:
     app.css's `.ac div` and this file's `.sqsugg button` are the same argument, and the shut
     section's own vertical padding is given back so 44px is the whole row and not 60. */
  .sq-grp>.h{min-height:var(--tap-min)}
  .sq-grp:not(.open){padding-top:0;padding-bottom:0}
}

/* ── THE SCOPE MARK ON A QUERY PILL (owner ruling 2026-08-23, common-icons.js SCOPE_ICON) ──
   ⚠ THE BOX IS THE CALLER'S, THE PAINT IS THE NODE'S. scopeMark hands back a `.sci` that
   carries its own fill/stroke precisely so it does NOT inherit `svg.navi` and with it the
   masthead's 14px cell. 13px is the size the marks were rasterised and compared at — the
   frame on `ocr` and the ragged last line on `text` are what survive the downscale, and both
   stop resolving below it. */
.sqpill .sci{width:13px;height:13px;flex:none;margin-right:1px}

/* ── A TAG PILL WEARS ITS NAMESPACE (owner ruling 2026-08-23: "the color of the tag box should
   be the color of the tag type") ──
   ⚖ IT REUSES `k-<namespace>`, THE SAME CLASS EVERY OTHER TAG CHIP IN THE ARCHIVE WEARS, so the
   colour comes from app-viewer.css's "LAST WORD ON TAG COLOUR" block and there is no second
   table of hues to drift. That block is also what makes a CONTENT WARNING outrank a namespace —
   a tag named `gore` with kind='media' must not render purple — and reusing the class inherits
   that ordering for free rather than re-deciding it here.
   ⚠ NO COLLISION WITH THE CRITERION KIND, which this element also carries as `k-tag`/`k-text`:
   the two vocabularies share no word (tag/text/by/is/related/raw vs general/character/meta/
   media/humor/nsfw/nsfl), and that is why one prefix can safely carry both.
   ⚠ ONLY THE BORDER IS ADDED HERE, and only when the pill is `is-only`. The text colour is
   already handled above; a preferred or excluded pill keeps the accent or the bad colour,
   because a namespace hue competing with an EXCLUSION would make "not this" read as decoration.
   ⚠ AND ONLY WHEN THE NAMESPACE IS KNOWN: `k-general` is the fallback and deliberately has no
   border rule, so an unrecognised tag looks exactly as it did before. */
.sqpill.k-tag.is-only[class*=" k-"]{border-color:currentColor}
/* ⚠ BOTH GLYPH CLASSES, BECAUSE THE PILL'S `#` MOVED FROM `.sg` TO `.vs` (F19) and this block
   is written TWICE in this sheet (STYLES.txt DUP=2) — fix one and the other stays broken. */
.sqpill.k-tag.is-only .sg,.sqpill.k-tag.is-only .vs{color:inherit}

/* ── THE RIGHT END OF THE CONTROL ROW (owner ruling 2026-08-23): the star is always last, the
   book is to its left, and with no search the book is last because a blank search cannot be
   saved. ──
   ⚠ NO `margin-left:auto` HERE, AND THAT IS THE WHOLE POINT. One attempt at this put the two
   actions after `saved` with an auto margin and produced an 806px gap on a 1280 desktop —
   forage-h5v's own defect, *"why is the saved button in the middle of nowhere"*, rebuilt from
   the other end and caught by searchbox.js the same afternoon. These sit on the row's ONE
   uniform gap like every other chip. */
/* ⚠ IT WEARS THE ROW'S OWN SHAPE, and one attempt at 44px is why that is written down. The four
   chips beside it are 27px tall by design and the row's law — held by searchbox.js — is that
   every chip on it is ONE height on ONE uniform gap. A 44px star broke both at 360px: a 17px
   height spread, and gaps of [-70,30,4,4] because it wrapped to a line of its own. The tap floor
   is a property of the ROW, which is settled where the row is, not re-decided by its last child. */
/* ⚠ IT WEARS `.sqnum`'s BOX EXACTLY — same padding, same line-height — because the row's law is
   that every chip on it is ONE height, and a star that differs by three pixels fails that census
   as loudly as one that differs by seventeen. Only the glyph size is its own. */
.sqctrl .star{flex:none;background:var(--sunk);border:1px solid var(--hair);cursor:pointer;
  color:var(--faint);font-size:11px;line-height:1;padding:5px 6px;position:relative;
  min-width:44px;display:inline-flex;align-items:center;justify-content:center}
/* ⚠ A REAL 44px OF WIDTH, NOT A HALO, and searchbox.js asks for it by name. WIDTH is free here —
   the row's laws are about HEIGHT and GAP — while a horizontal overhang would reach across the
   uniform 4px gap and steal the tap of the chip beside it. The vertical floor still comes from
   the coarse-pointer block at the end of this sheet, on the shape, like every other control. */
/* 🐛 THE `display:` vs `[hidden]` TRAP, SIXTH TIME IN THIS SHEET AND IT CAUGHT ME TOO. An
   inline-flex beats the UA's `[hidden]{display:none}`, so without this the ☆ would draw on a
   bare feed — where there is nothing to keep, which is the owner's own reason for it not being
   there: *"you can't save a blank search"*. searchbox.js asks it as "draws NOTHING, not merely
   hidden", which is the only form of the question that catches this. */
.sqctrl .star[hidden]{display:none}
.sqctrl .star.on{color:var(--accent);border-color:var(--accent-deep)}
/* the book replaces the WORD `saved`; the count beside it says what the book holds */
.sqnum .bk{display:inline-flex;align-items:center}
.sqnum .bk .sci{width:13px;height:13px}

/* ── `narrow` AS AN ARROW WITH A COUNT ON IT (owner ruling 2026-08-23) ──
   ⚖ IT KEEPS `.sqnum`, WHICH IS ITS SHAPE AND ITS TAP FLOOR. The row's law is that every chip is
   one of two shapes; this changed what the chip SAYS, not what it is, so it keeps the class and
   inherits the coarse-pointer halo at the end of this sheet by wearing a shape rather than by
   being remembered by name — which is the whole reason forage-401 moved the floor onto shapes.
   ⚠ THE BADGE SITS ON THE ARROW, not beside it, which is where the ~40px comes from. It is the
   same `#sqcount` node the drawer has always written, so there is still exactly one writer. */
.sq-narrow .ar{display:inline-flex;align-items:center}
.sq-narrow .ar .sci{width:13px;height:13px}
/* ⚠ THE BADGE SITS INSIDE THE BUTTON'S BOX, RAISED, NOT HUNG OFF ITS CORNER. A corner badge at
   `right:-5px` overflows the padding box — searchbox.js caught it as a control "drawn narrower
   than its own label" (scrollWidth past clientWidth), which is the same measurement that found
   the defect the owner photographed. And on this row an overhang would reach across the uniform
   4px gap onto the chip beside it, which is the sideways theft the whole sheet avoids. */
.sq-narrow .n.badge{align-self:center;margin:0;min-width:12px;padding:1px 3px;
  display:inline-flex;align-items:center;justify-content:center;border-radius:6px;
  background:var(--accent-deep);color:var(--void);font-size:8.5px;line-height:1;letter-spacing:0}
/* a count of zero is not news — the arrow alone already says "there is a drawer here" */
.sq-narrow:not(.armed) .n.badge{display:none}
/* ⛔ THE WORD IS THE DEFAULT AND THE PHONE IS THE EXCEPTION (owner 2026-08-23, with a desktop
   screenshot: *"the narrow tab is too small for desktop"*). This chip became an arrow and a
   count when the owner asked for a dropdown arrow, and that shape is right on a PHONE — the
   row's 360px budget has 7.6px of slack in its widest state and every chip is fighting for the
   same line. On a 1920 desktop the result was a 20px unlabelled chevron: the SMALLEST control
   on the row, the only one with no word, sitting beside 900px of empty column — and it is the
   door to the entire narrowing drawer. The most important control on the row was the hardest
   one to find.
   ⚖ THIS IS THE SHAPE THE SHEET ALREADY DECLARED. `.sqnum`'s own comment a hundred lines up
   says the count chips are *"`narrow N` and `saved N` — a label beside a number"*. `saved` kept
   its label; `narrow` lost its one when it grew the arrow, and nothing re-read the rule. This
   restores what was written down rather than inventing a desktop variant.
   ⚖ WRITTEN AS max-width:999px — THE BURGER RUNG — RATHER THAN A min-width OF ITS OWN. The
   first attempt at this invented `min-width:900px` and mobilebreakpoints.test.js caught it: a
   stray breakpoint is a ratchet here, and 999 is already this archive's word for "the masthead
   stops being a phone masthead". The compact shape now rides the rung that already means what
   it means, and no new width joins the pile.
   ⚠ WIDTH, NOT POINTER, and the distinction is live — forage-67lg is an open complaint about
   getting it the other way round. Whether there is ROOM for a word is a width question. Whether
   a target is big enough for a finger is a pointer question, answered separately by the
   coarse-pointer halos at the end of this sheet, which this does not touch. A touch tablet at
   1100px gets the word, and that is correct rather than incidental: it is exactly the device
   with no hover to explain a bare glyph.
   ⚠ THE BADGE MOVES WITH IT. Beside a word it is an inline count on the row's own line; alone
   on an arrow it is the raised corner mark, which is where its ~40px target comes from. */
@media(max-width:999px){
  .sq-narrow .w{display:none}
  .sq-narrow .n.badge{align-self:flex-start;margin:-1px 0 0 1px}
}

/* the clear chip is icon-only, so it takes the same box the other marked chips do */
.sq-clear .bk{display:inline-flex;align-items:center}
.sq-clear .bk .sci{width:13px;height:13px}
.sqctrl .sq-clear[hidden]{display:none}

/* ═══ THE QUEUE MARK — A HANDLED CARD DIMS IN PLACE ════════════════════════════════════════
   Owner ruling 2026-09-03: *"I want to hide posts with that tag so I can quickly get to posts
   that need that tag added."* A search with an EXCLUDED tag is a work queue, so a card that
   has just been given that tag has left it — and saying nothing leaves a member re-reading
   forty cards to find which ones they already did.
   ⚖ THE POSTURE IS BORROWED, NOT INVENTED: `.tp-card.is-done` (tagpoll.css:32) is this
   archive's existing word for "handled, still on screen", and it is `opacity:.55`. One
   vocabulary for one idea.
   ⛔ AND NO `pointer-events:none` — that is the one line `.tp-card.is-done` adds for its own
   buttons and the one line this must NOT copy. A done card is exactly the card you retract
   from when you mis-brushed it, so its bar, its ctrl+click and its link all stay live.
   ⛔ AND THE CARD IS NOT REMOVED. Reflowing a grid under a thumb mid-brush is the mis-tap the
   affordance ruling exists to prevent; it drops out on the next search, which is a moment the
   member chose. */
.tilecell.sq-done{opacity:.55}

/* ═══ WHICH TAGS GET A CURATION BAR — THE CHIP AND ITS DROPDOWN ════════════════════════════
   Owner ruling 2026-09-03 (SPEC_crowd_tags § TAG BRUSH): *"allow showing and hiding of
   different types of tags and specific tags … a drop down on the main search page."*
   ⚖ NO THIRD SHAPE. `.sqtags` wears `.sqnum` — the row has exactly two shapes and a control
   that OPENS a panel while showing a COUNT is `narrow`, which is a `.sqnum` already. So the
   height is the row's, the 44px coarse halo arrives with the class, and NOTHING GOT BIGGER.
   ⚠ THE `#` IS `.sg`, the same mark the pills and the drawer options wear — one glyph per
   feature, everywhere it appears (§1.1 / R2). It is a TEXT mark and not a drawing because that
   is what a tag's mark IS in this language; the registry owns the drawn ones. */
.sqtags .sg{color:var(--accent);font-weight:700}
/* ⚖ THE CHIP IS A DESKTOP CONTROL, AND THIS PARAGRAPH USED TO SAY THE OPPOSITE — it argued
   that `tags` is four characters and the row could hold it at 360. MEASURED, THE ROW CANNOT
   HOLD THE CHIP AT ALL at phone width, with or without its word:

     390x844, `^#cat sort:votes dir:asc`   client 362px   chips 387px
       narrow 45 · sort 121.7 · when 40.8 · TAGS 44.1 · saved 71.1 · ☆ 44   (+4px gaps)

   Without the chip that is 338.9 — 23px of slack, which is the budget this row was designed
   to (the `.sqset` note above says 7.6px at 360). The smallest honest version of this control
   is a mark and a count at 44px, so there is no shape of it that fits.
   ⛔ AND THE STAR IS NOT THE THING THAT GIVES. `deploy.sh [0b/6]` (`searchbox.js`, 390 with a
   REAL touch pointer) went red on five saved-search assertions the moment this chip shipped:
   the ☆ was drawn at right=400.8 inside a 362px scroller, `elementFromPoint` at its centre
   answered `app`, and a real tap saved nothing. `.click()` on the node still worked, which is
   why every DOM-level check stayed green. The ☆ is a shipped feature and a full-size FIRST-TAP
   target (owner ruling 2026-08-23: it is always last, and it only fits at all because `narrow`
   became an arrow and a badge in the same change).
   ⚖ SO THE CHIP GOES WHERE THE CONSOLE IS NOT, which is the ruling read literally rather than
   a retreat. The owner asked for TWO surfaces — *"a drop down on the main search page and a
   new tab in the search page you can access with the bottom right button"* — and the console
   is gated on `xpEnabled()`, i.e. `!HOVER_CAPABLE`: it EXISTS ONLY ON A COARSE POINTER. The
   two surfaces are therefore the two devices. A phone gets the console's `tags` tab, which is
   strictly more (it carries the standing lists as well); a desktop gets this chip.
   ⚠ AND `min-width:1000px` BESIDE IT, because the row's width is the actual constraint and a
   fine pointer in a 390px window has the same 23px. 999 is already this archive's word for
   "the row is a phone row" — ⛔ no new width joins the pile. A desktop window narrower than
   that has neither surface for the length of time it is that narrow; the preference still
   applies, it is simply not editable from the grid until the window is wider or the phone is
   in hand. Said plainly rather than papered over. */
.sqtags{display:none}
@media (hover:hover) and (pointer:fine) and (min-width:1000px){.sqtags{display:inline-flex}}
/* a count of zero is not news — the same argument `.sq-narrow`'s zero badge already makes */
.sqtags .n{color:var(--faint)}
.sqtags.armed .n{color:var(--accent)}

.sqpop.sqtagpop{min-width:min(92vw,240px);max-width:min(92vw,264px)}
.sqtagset{display:flex;flex-direction:column;min-width:0}
.sqtag-grp{display:flex;flex-direction:column;border-bottom:1px solid var(--hair)}
.sqtagset .sqtag-grp:last-child{border-bottom:0}
.sqtag-grp .h{font-size:7.5px;line-height:1;letter-spacing:calc(.16em * var(--track));
  text-transform:var(--caps);color:var(--faint);padding:7px 9px 5px}
/* THE THREE STRENGTH SWITCHES. ⚖ They read as a SET — one row, equal thirds — because the
   question is one question with three answers, and stacking them would say three questions.
   🐛 EVERY SELECTOR IN THIS BLOCK IS SCOPED TO `.sqtagpop`, AND THAT IS NOT TIDINESS. This
   sheet's own `.sqpop button{background:none;border:0}` is 0,1,1 and a bare `.sq-sw` is 0,1,0,
   so the shared rule WINS and every switch draws borderless — three words with no box, which
   is exactly what the first build photographed. The sheet already warns about `font:` shorthand
   reaching these chips for the same reason; this is the border half of that trap. */
/* ⚠ `display:flex` IS DECLARED HERE, not inherited. `.sqpop .keys` supplies it inside the
   dropdown and there is no `.sqpop` in the console, so the three switches fell back to inline
   boxes sized by their words and stopped reading as a set — photographed at 390. */
.sqtagset .keys{display:flex;flex-direction:row;padding:0 7px 8px;gap:5px}
.sqtagset .sq-sw{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:3px;
  position:relative;border:1px solid var(--hair);background:var(--sunk);padding:6px 3px;
  font-size:8.5px;letter-spacing:calc(.06em * var(--track))}
.sqtagset .sq-sw .sg{color:var(--faint);font-weight:700;font-size:10px;line-height:1}
.sqtagset .sq-sw .w{font-size:8.5px}
/* ⚖ ON IS THE ACCENT BORDER, the same "this is not the default" rule `.sqset.on` makes on the
   row above — one state vocabulary for the whole surface. OFF is not struck or greyed further:
   an unchecked strength is a choice, not a broken control. */
.sqtagset .sq-sw.on{border-color:var(--accent-deep);color:var(--ink)}
.sqtagset .sq-sw.on .sg{color:var(--accent)}

/* ONE ROW PER TAG IN THIS QUERY: the name, then its two standing verdicts. */
.sqtagset .sqtag-row{display:flex;align-items:center;gap:5px;padding:3px 9px 5px}
.sqtagset .sqtag-row .v{flex:1;min-width:0;font-size:10.5px;color:var(--ink);text-transform:none;
  letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sqtagset .sqtag-row .v .vs{color:var(--accent);font-weight:700;margin-right:1px}
.sqtagset .sq-pin{flex:none;position:relative;border:1px solid var(--hair);background:var(--sunk);
  padding:5px 6px;font-size:8px;letter-spacing:calc(.1em * var(--track))}
.sqtagset .sq-pin.on{border-color:var(--accent-deep);color:var(--ink)}
.sqtagset .sqtag-note{padding:6px 9px 8px;font-size:8px;line-height:1.5;color:var(--faint);
  text-transform:none;letter-spacing:0}

/* ⚖ THE 44px FLOOR FOR THE PANEL'S OWN CONTROLS, spent as a halo and declared inside a COARSE
   block — this sheet's own law, and the reason mobiletaps.test.js can see it at all (it reads
   the cascade for this shape and looks only inside `@media(pointer:coarse)`). ⚠ `left:0;right:0`
   and a HEIGHT: these tile vertically inside the panel, so a horizontal halo would overhang the
   control beside it. The strength switches sit side by side, so they take the height halo and
   keep their own width, exactly as `.sqnum` does on the row above. */
@media(pointer:coarse){
  .sqtagset .sq-sw::after,.sqtagset .sq-pin::after,.sqtags::after{
    content:"";position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
    height:var(--tap-min)}
}

/* ═══ THE SAME PANEL IN THE SEARCH CONSOLE (owner ruling 2026-09-03, the second surface) ═════
   ⚖ ONE SET OF RULES, HUNG OFF `.sqtagset` RATHER THAN OFF EITHER HOST. In the dropdown they
   have to beat `.sqpop button` (0,1,1); in the console they sit under a different sheet
   entirely. A container class is 0,2,0 in both and needs no `!important` in either — which is
   the whole reason the switches are not styled by their own bare class.
   ⚠ THE CONSOLE IS A FULL-WIDTH COLUMN, not a 264px menu, so the only thing that changes is the
   frame: a border to say the block is one thing, and room for the add field. */
.xpc-body .sqtagset{border:1px solid var(--hair);background:var(--sunk)}
.xpc-tagnote{font-size:9px;line-height:1.5;color:var(--faint);padding:0 0 7px;
  text-transform:none;letter-spacing:0}
/* THE ADD FIELD. `position:relative` is load-bearing: `setupAutocomplete` writes its list into
   the `.ac` beside the input, and the archive's `.ac` is absolutely positioned. */
.sqtag-add{position:relative;padding:5px 9px 8px}
.sqtag-add input{width:100%;box-sizing:border-box;background:var(--void);border:1px solid var(--hair);
  color:var(--ink);font:inherit;font-size:11px;padding:7px 8px;min-height:var(--tap-min)}
.sqtag-add input:focus-visible{border-color:var(--accent-deep);outline:none}
.sqtag-ac{left:9px;right:9px;width:auto}
