/* Counter, the whole stylesheet.
   Lifted out of index.html on 2026-09-13. It had been inline by design, which cost
   nothing while the page was small and cost the file 52 percent of its editing budget
   once it was not. The service worker caches it with the rest of the shell, so a
   returning creator still pays one request for the lot.
   Load order is unchanged: this is the only stylesheet and it loads before app.js. */
/* ============================================================
   Counter brand kit v2, direction "Daylight"
   Locked 2026-09-01. Kira Solomon, creative direction.
   Every value below is a token. Nothing is hardcoded downstream.
   ============================================================ */
:root{
  /* Counter brand kit v2, direction "Daylight". Locked 2026-09-01 after a
     look at Trybe, Cruva and Euka. Dark brass read as a trading terminal.
     Warm paper differentiates from Cruva and Trybe's cool lavender-white, and
     coral is unclaimed in a set where Cruva and Trybe run indigo, Euka runs
     acid lime and TikTok runs cyan with hot pink.
     Every value here clears WCAG AA on its own ground. Run `node tools/contrast.js`
     before changing any of them. It checks 18 pairs and exits non-zero on a failure. */
  /* Counter. A cool paper with a faint green cast, deliberately not the warm cream
     that every generated interface reaches for. Three of the four tools in this
     category run violet and the fourth runs acid lime, so ivy sits alone. */
  --ground:#F1F3EE;
  --surface:#FFFFFF;
  --surface-2:#E7EBE3;
  --rule:#DBE0D5;
  --rule-2:#C3CAB9;

  --ink:#121711;
  --ink-2:#495145;
  --ink-3:#636C5F;
  --dead:#636C5F;

  /* Ivy is the brand and it is also what settled money looks like, on purpose.
     Money that landed is money that is yours, so the two mean the same thing and
     the page does not need a second green to say it. */
  --accent:#0F5132;
  --accent-fill:#146239;
  --accent-hot:#187845;
  --accent-soft:#E1EDE6;
  --on-accent:#FFFFFF;
  /* Quieter text on an ivy ground, for the sign in field. 6.81 and 5.83 on --accent. */
  --on-accent-2:#CFE0D6;
  --on-accent-3:#B9D2C3;

  --settled:#0F5132;
  --late:#8A5A00;
  /* Reserved for money that went backwards. Never decorative, so a red on this
     page always means something was taken away. */
  --lost:#A32E1E;

  /* Three faces, three jobs. Newsreader carries anything that states a fact, because a
     figure set in a serif reads as a fact rather than a graphic, which is the whole job
     of this app. Public Sans is deliberately plain underneath it. JetBrains Mono holds
     every number and label, where its tabular figures keep columns honest. */
  --display:'Newsreader', Georgia, 'Times New Roman', serif;
  --sans:'Public Sans', system-ui, -apple-system, sans-serif;
  --mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Apple and Android both ask for 44. The app shipped 36 and 30, which is close
     enough to look fine and not close enough to hit on a moving bus. */
  --tap:44px;

  --r:10px;
  --r-sm:7px;
  --gut:20px;
  --bar-h:60px;
  --offbar-h:36px;
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
  background:var(--ground);
  color:var(--ink);
  font-family:var(--sans);
  font-size:15px;
  line-height:1.45;
  -webkit-font-smoothing:antialiased;
  min-height:100vh;
}
button,input,select,textarea{font-family:inherit;color:inherit;}
a{color:var(--accent);}

/* ---------- form controls ----------
   Browsers draw their own spinners, file buttons and dropdown arrows, and every one of
   them is a different shape from ours. These reset the four that show. */

/* Spinner arrows on a money field are a way to get the wrong number one click at a time. */
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;}

/* "Choose File, no file chosen" in the browser's own grey. The button becomes ours and
   the filename beside it becomes a quiet caption. */
input[type=file]{font-size:12.5px; color:var(--ink-3); max-width:100%;}
input[type=file]::file-selector-button{
  font-family:inherit; font-size:12.5px; font-weight:600;
  background:var(--surface); color:var(--ink-2);
  border:1px solid var(--rule-2); border-radius:var(--r-sm);
  padding:7px 12px; margin-right:10px; cursor:pointer;
}
input[type=file]::file-selector-button:hover{border-color:var(--ink-3); color:var(--ink);}
/* The native "No file chosen" sits after the button in a width we do not control and a
   string we cannot change, so in a narrow column it truncates to "N...". Hide it and say
   what was actually chosen underneath instead. */
input[type=file]{color:transparent; width:100%;}
input[type=file]::-webkit-file-upload-button{color:var(--ink-2);}
.file-name{
  display:block; margin-top:6px; font-family:var(--mono); font-size:11px;
  color:var(--ink-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* Keyboard focus. Every interactive thing gets the same ring, drawn outside the element
   so it never shifts layout. Mouse clicks do not trigger it. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:var(--r-sm);
}

/* The date picker icon sits at full contrast by default and pulls the eye off the value. */
input[type=date]::-webkit-calendar-picker-indicator{opacity:.45; cursor:pointer;}
input[type=date]:hover::-webkit-calendar-picker-indicator{opacity:.8;}
input[type=date]{min-height:38px;}

/* One dropdown arrow, ours, in the ink colour, instead of each platform's own. */
select{
  -webkit-appearance:none; -moz-appearance:none; appearance:none;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236B7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 11px center;
  padding-right:30px;
}
select.mini{background-position:right 8px center; padding-right:24px;}

.num{font-family:var(--mono); font-variant-numeric:tabular-nums; letter-spacing:-0.01em;}
.eyebrow{
  font-family:var(--mono); font-size:10.5px; font-weight:500;
  letter-spacing:0.16em; text-transform:uppercase; color:var(--ink-3);
}

/* ---------- chrome ---------- */
.topbar{
  position:sticky; top:0; z-index:40;
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:13px var(--gut);
  background:var(--ground);
  border-bottom:1px solid var(--rule);
}
.lockup{display:flex; align-items:center; gap:9px;}
.lockup svg{display:block;}
/* The mark carries the colour, so the word does not have to. Colouring half a
   wordmark is the move of a brand that is not sure of itself. */
.wordmark{
  font-family:var(--display); font-weight:700; font-size:20px;
  letter-spacing:-0.02em; color:var(--ink);
}
.who{display:flex; align-items:center; gap:12px; font-size:12.5px; color:var(--ink-3);}
.who .mail{max-width:170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.ghost-btn{
  background:none; border:1px solid var(--rule-2); color:var(--ink-2);
  padding:9px 13px; border-radius:var(--r-sm); font-size:12.5px; cursor:pointer;
  min-height:var(--tap);
}
.ghost-btn:hover{border-color:var(--ink-3); color:var(--ink);}

nav.rail{
  display:flex; gap:0; padding:0 var(--gut);
  border-bottom:1px solid var(--rule);
  position:sticky; top:52px; z-index:35; background:var(--ground);
  overflow-x:auto; scrollbar-width:none;
}
nav.rail::-webkit-scrollbar{display:none;}
nav.rail button{
  position:relative;
  display:inline-flex; align-items:center; gap:8px;
  background:none; border:none; cursor:pointer; white-space:nowrap;
  padding:15px 2px 14px; margin:0 16px 0 0; min-height:var(--tap);
  font-family:var(--sans); font-size:14px; font-weight:500;
  letter-spacing:-0.005em; color:var(--ink-3);
  transition:color .15s ease;
}
nav.rail button:last-child{margin-right:0;}
/* The indicator is sized to the label rather than to the padded hit area. A rule that runs
   the full width of the button is what the browser gives you for nothing. One that matches
   the word is what somebody chose. */
nav.rail button::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:2px;
  background:var(--accent-hot); border-radius:2px 2px 0 0;
  transform:scaleX(0); transform-origin:center;
  transition:transform .18s ease, background-color .18s ease;
}
nav.rail button:hover{color:var(--ink-2);}
nav.rail button:hover::after{transform:scaleX(1); background:var(--rule-2);}
nav.rail button[aria-current="page"]{color:var(--ink); font-weight:600;}
nav.rail button[aria-current="page"]::after{transform:scaleX(1); background:var(--accent-hot);}
nav.rail button:focus-visible{outline:2px solid var(--accent); outline-offset:-3px; border-radius:4px;}

/* There was no mechanism anywhere in this file for saying something to a screen reader
   that is not already drawn, and no way past the five tabs and the account menu to the
   content. Both of those are one utility class each. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
}
.skip{
  position:absolute; left:var(--gut); top:-60px; z-index:120;
  background:var(--accent); color:var(--on-accent);
  font-size:13px; font-weight:600; padding:9px 14px; text-decoration:none;
  transition:top .15s ease;
}
.skip:focus{top:8px;}
.wrap:focus{outline:none;}
.wrap{max-width:1000px; margin:0 auto; padding:0 var(--gut) 96px;}
.view{display:none;}
.view.on{display:block;}

/* ---------- the tape ---------- */
/* The lead. Deliberately not a money tape: the figure is a serif line of text, not a
   mono numeral, because the answer to "what should I record today" is a thing, not an
   amount. It carries the same eyebrow and rule as the tape so the two read as one family
   rather than two competing headers. */
.lead{padding:30px 0 24px; border-bottom:1px solid var(--rule);}
.lead-line{
  font-family:var(--display); font-size:clamp(27px, 6vw, 40px); font-weight:700;
  line-height:1.12; letter-spacing:-0.02em; color:var(--ink);
  margin:9px 0 0; max-width:20ch; text-wrap:balance;
}
/* The lead said "due in 3 days" in the past-due colour, and at two days it turned --lost,
   which the kit reserves for money that went backwards. The urgency is already carried by
   the bolded day count in the why line. */
.lead-line.bad,.lead-line.warn{color:var(--ink);}
.lead-line.quiet{color:var(--ink-2); font-weight:600;}
.lead-why{
  font-size:15.5px; color:var(--ink-2); margin:13px 0 0; max-width:52ch; line-height:1.5;
}
.lead-why strong{color:var(--ink); font-weight:600;}
#lead-go{margin-top:16px;}
.tape{padding:34px 0 26px; border-top:1px solid var(--rule); border-bottom:1px solid var(--rule); margin-top:8px;}
.tape-num{
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:clamp(46px, 12vw, 76px); font-weight:600; line-height:1;
  color:var(--accent); letter-spacing:-0.035em; margin:9px 0 0;
}
.tape-unit{
  font-family:var(--mono); font-size:12px; color:var(--ink-3); margin-top:8px;
}
.tape-read{
  font-size:16px; color:var(--ink-2); margin:16px 0 0; max-width:52ch; line-height:1.5;
}
.tape-read strong{color:var(--ink); font-weight:600;}
.tape-read.ok{color:var(--settled);}
.tape-read.warn{color:var(--late);}
.tape-read.bad{color:var(--lost);}
/* Eight of ten reviewers could not tell which of five money words meant which, and none
   of them could work out why a campaign was missing from the total without reading the
   rule on another tab. The rule now sits under the figure it governs. */
.tape-basis{
  font-family:var(--mono); font-size:11.5px; letter-spacing:.01em;
  color:var(--ink-3); margin:10px 0 0; line-height:1.6; max-width:56ch;
}
.split{
  display:flex; height:5px; border-radius:999px; overflow:hidden;
  background:var(--rule); margin-top:20px; max-width:520px;
}
.split i{display:block; height:100%;}
.split i.a{background:var(--settled);}
.split i.b{background:var(--lost);}
.split i.c{background:var(--late);}
.split-key{
  display:flex; gap:20px; margin-top:9px; font-family:var(--mono);
  font-size:11px; color:var(--ink-3);
}
.split-key span::before{
  content:""; display:inline-block; width:7px; height:7px; border-radius:2px; margin-right:6px;
}
.split-key .k1::before{background:var(--settled);}
.split-key .k2::before{background:var(--lost);}
.split-key .k3::before{background:var(--late);}

/* ---------- alert strip ---------- */
.strip{
  display:flex; align-items:center; gap:13px; flex-wrap:wrap;
  padding:13px 15px; margin-top:22px;
  border:1px solid var(--rule-2); border-left:3px solid var(--accent-hot);
  border-radius:var(--r-sm); background:var(--surface);
  font-size:13.5px; color:var(--ink-2);
}
.strip.warn{border-left-color:var(--late);}
.strip.calm{border-left-color:var(--settled);}
.strip strong{color:var(--ink); font-weight:600;}
.strip .num{color:var(--ink);}

/* ---------- sections ---------- */
section{margin-top:38px;}
.sec-head{
  display:flex; align-items:baseline; justify-content:space-between; gap:12px;
  padding-bottom:11px; border-bottom:1px solid var(--rule-2);
}
.sec-head h2{font-family:var(--display); margin:0; font-size:17px; font-weight:700; letter-spacing:-0.01em;}
.sec-head .note{font-family:var(--mono); font-size:11px; color:var(--ink-3);}
/* A section that is data entry rather than a record starts closed. The tab should open
   on your work, not on an empty form. */
.sec-head .fold{
  font-family:var(--sans); font-size:12px; font-weight:600; color:var(--ink-3);
  background:none; border:1px solid var(--rule-2); border-radius:var(--r-sm);
  padding:5px 10px; cursor:pointer; min-height:var(--tap); white-space:nowrap;
}
.sec-head .fold:hover{border-color:var(--ink-3); color:var(--ink);}
.sec-head .fold[aria-expanded="true"]{border-color:var(--ink-3); color:var(--ink);}

/* ---------- rows ---------- */
.rows{list-style:none; margin:0; padding:0;}
.row{
  display:grid; grid-template-columns:40px 1fr auto; gap:5px 13px;
  padding:14px 0; border-bottom:1px solid var(--rule); align-items:baseline;
}
.row.plain{grid-template-columns:1fr auto;}
.row .tile{grid-column:1; grid-row:1 / span 2; align-self:start; margin-top:-2px;}
.row.plain .title{grid-column:1;}
.row.plain .meta{grid-column:1;}
.row.plain .side{grid-column:2;}
.row:last-child{border-bottom:none;}
.row .title{grid-column:2; grid-row:1; font-weight:700; font-size:15px; letter-spacing:-0.005em;}
.row .money{
  grid-column:3; grid-row:1;
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:19px; font-weight:500; text-align:right; white-space:nowrap;
}
.row .money.settled{color:var(--settled);}
.row .meta{
  grid-column:2; grid-row:2; display:flex; flex-wrap:wrap; gap:12px;
  font-family:var(--mono); font-size:11.5px; color:var(--ink-3);
}
.row .meta b{font-weight:500; color:var(--ink-2);}
/* The offer as she pasted it. Saved on every campaign since the reader shipped and never
   shown back, so a creator could not check what a parsed figure came from or what a brand
   actually promised. Placed on its own grid row spanning to the end: every other child of
   .row carries an explicit grid-column, and an unplaced item here would reflow them. */
.row .orig{grid-column:2 / -1; grid-row:3; margin-top:2px;}
.row .orig summary{
  font-family:var(--mono); font-size:11.5px; color:var(--ink-3); cursor:pointer;
  min-height:var(--tap); display:flex; align-items:center; gap:6px;
}
.row .orig summary:hover{color:var(--ink-2);}
.row .orig summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px;}
.row .orig p{
  margin:0 0 10px; font-size:14px; line-height:1.45; color:var(--ink-2);
  white-space:pre-wrap; overflow-wrap:anywhere;
  background:var(--surface-2); border-radius:var(--r-sm); padding:11px 13px;
  max-height:230px; overflow-y:auto;
}

/* The amendment trail. A brand cutting a rate after she took the deal used to overwrite
   the old number, so the cut was invisible by the time it mattered. Own grid row. */
.row .trail{grid-column:2 / -1; grid-row:4; margin-top:2px;}
.row .trail summary{
  font-family:var(--mono); font-size:11.5px; color:var(--late); cursor:pointer;
  min-height:var(--tap); display:flex; align-items:center; gap:6px;
}
.row .trail summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px;}
.row .trail ol{margin:0 0 10px; padding:0 0 0 16px; font-size:13.5px; color:var(--ink-2);}
.row .trail li{margin:3px 0;}
.row .trail .was{text-decoration:line-through; text-decoration-color:var(--rule-2); color:var(--ink-3);}
.row .trail .on{font-family:var(--mono); font-size:11.5px; color:var(--ink-3);}
/* Editing reuses this sheet. The paste reader belongs to adding, so it stands down
   rather than offering to overwrite fields she just opened. */
#cap-sheet.editing .dlg-note,#cap-sheet.editing .hint,
#cap-sheet.editing #paste,#cap-sheet.editing .sheet-actions{display:none;}
#cap-trail{font-size:13.5px; color:var(--ink-2); margin:0 0 4px;}
#cap-trail ol{margin:6px 0 0; padding:0 0 0 16px;}
#cap-trail li{margin:3px 0;}
.row .meta .late{color:var(--late);}
/* The landed figure sits in the row's own control cluster, beside the status it belongs to.
   It is a money field, so it takes the mono figures every other money field uses. */
.settled-in{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
}
.settled-in span{
  font-family:var(--mono); font-size:10px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-3);
}
.settled-in input{
  width:82px; font-family:var(--mono); font-variant-numeric:tabular-nums;
  text-align:right;
}
@media (max-width:520px){ .settled-in input{width:68px;} }
.row .side{grid-column:3; grid-row:2; text-align:right; display:flex; gap:8px; justify-content:flex-end; align-items:center;}
.row .summary{
  grid-column:2 / -1; font-size:13.5px; color:var(--ink-2);
  max-width:74ch; line-height:1.5; margin-top:2px;
}

/* Brand identity. One tile shape for every brand, logo when a curator has
   attached one, a monogram in a cool family when not. The monogram palette
   deliberately excludes the coral accent, settled green and late amber so those stay
   purely semantic. */
.tile{
  width:40px; height:40px; border-radius:11px; overflow:hidden;
  display:grid; place-items:center; flex:none;
  background:var(--surface); border:1px solid var(--rule-2);
}
.tile img{max-width:70%; max-height:70%; display:block;}
.tile.lit img{
  max-width:78%; max-height:78%;
  background:#1B1F26; border-radius:5px; padding:3px;
}
.tile i{display:none; font-style:normal; font-weight:800; font-size:13px; letter-spacing:-0.02em;}
.tile:not(:has(img)) i{display:block;}
/* Monogram tints, all in the paper and ivy family. The blue, lavender and pink here
   were left from the first kit and read as another product on the page. Every letter
   clears AA on its own tile, checked with tools/contrast.js. */
.tile.c0{background:#E3EDE6; border-color:#CBDCD2; color:#16543A;}
.tile.c1{background:#F1E8E2; border-color:#E1D3CA; color:#7A4630;}
.tile.c2{background:#E5EAEC; border-color:#D2D9DD; color:#394A52;}
.tile.c3{background:#F2ECDD; border-color:#E3D9C3; color:#6A5215;}
.tile.c4{background:#E9EDE1; border-color:#D7DECB; color:#465533;}
.tile.c5{background:#EAEAE5; border-color:#D8D8D2; color:#4C4F47;}
.tile.sm{width:30px; height:30px; border-radius:8px;}
.tile.sm i{font-size:11px;}
/* A wordmark-shaped logo (wider than 2.2 to 1) gets the tile's width rather than
   its height, so it reads as the brand's colour and shape beside the name. The mark
   itself is never cropped or recoloured. */
.tile img.word{max-width:86%; max-height:56%;}
.tile img{opacity:0; transition:opacity .22s ease;}
.tile img.in{opacity:1;}

/* The photo column. The one honest photo source in this app is the shot a creator
   takes when a sample lands, so a row with one leads with it in place of the tile.
   Portrait, the shape a phone takes it in. */
.shot{
  width:40px; height:54px; border-radius:9px; overflow:hidden; flex:none;
  background:var(--surface-2); border:1px solid var(--rule-2);
  grid-column:1; grid-row:1 / span 2; align-self:start; margin-top:-2px;
}
.shot img{width:100%; height:100%; object-fit:cover; display:block; opacity:0; transition:opacity .22s ease;}
.shot img.in{opacity:1;}
.row.has-shot{grid-template-columns:40px 1fr auto;}
/* The visually hidden file input behind each sample thumbnail. It must carry the type in
   the selector: the generic `input[type=file]{width:100%}` above sets the width for the
   visible pickers in forms, and a bare `.shot-in` lost to it, so this input rendered
   390px wide inside a row and pushed the whole document to 457px on a 390px phone. The
   page scrolled sideways under a fixed bottom nav, which reads as broken rather than wide.
   Caught by a render check at iPhone width on 2026-09-11. */
input[type=file].shot-in{position:absolute; width:1px; height:1px; opacity:0; pointer-events:none;}

/* Trends rows. Same ruled row, the figure sits where money sits. */
.row .fig{
  grid-column:3; grid-row:1;
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:17px; font-weight:500; text-align:right; white-space:nowrap; color:var(--accent);
}
/* What the figure counts. Small, directly under it, so the number stays the loud part
   and stops being a riddle. */
.row .fig-note{
  grid-column:3; grid-row:2; align-self:start;
  font-family:var(--sans); font-size:11px; line-height:1.3; color:var(--ink-3);
  text-align:right; margin-top:3px; max-width:15ch; justify-self:end;
}
.row.plain .fig{grid-column:2;}
.row.plain .fig-note{grid-column:2;}
.row.plain .summary{grid-column:1 / -1;}
.row .meta a{color:var(--ink-2); text-decoration:none; border-bottom:1px solid var(--rule-2);}
.row .meta a:hover{color:var(--ink); border-bottom-color:var(--ink-3);}
.kind{display:inline-flex; align-items:center; gap:5px;}
.kind::before{content:""; width:6px; height:6px; border-radius:1px; background:currentColor;}
.k-video{color:var(--accent);}
.k-policy{color:var(--late);}
.k-market{color:var(--ink-2);}
.k-tool{color:var(--settled);}
/* Seasons. A 140 by 34 sparkline per topic, three prior years in rule grey and this
   year in coral. Data never animates. The sentence beside it carries the meaning, so
   the drawing is aria-hidden. */
.spark{grid-column:1; grid-row:1 / span 2; align-self:start; width:180px; height:44px; display:block; margin-top:2px;}
.spark .past{fill:none; stroke:var(--rule-2); stroke-width:1.2;}
.spark .now{fill:none; stroke:var(--accent-hot); stroke-width:1.6;}
.spark .tick{stroke:var(--rule); stroke-width:1;}
.row.season{grid-template-columns:180px 1fr auto;}
.row.season .fig{color:var(--ink);}
.row.season .fig b{color:var(--accent); font-weight:600;}
@media (max-width:760px){
  .row.season{grid-template-columns:1fr auto;}
  .spark{grid-column:1 / -1; grid-row:4; width:100%; max-width:260px; margin-top:8px; justify-self:start;}
  .row.season .title{grid-column:1;}
  .row.season .fig{grid-column:2;}
  .row.season .meta{grid-column:1 / -1;}
  .row.season .summary{grid-column:1 / -1;}
}
.tape-src{font-family:var(--mono); font-size:11px; color:var(--ink-3); margin-top:12px;}
.tape-src a{color:var(--ink-3); text-decoration:none; border-bottom:1px solid var(--rule-2);}
.tape-src a:hover{color:var(--ink);}

/* Motion. Theo Marquez. Opacity only, nothing longer than the job, and every
   transition dies under reduced motion. Money figures never animate. */
.view.on{animation:viewin .16s ease-out;}
@keyframes viewin{from{opacity:0} to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none !important; transition:none !important;}
  .tile img,.shot img{opacity:1;}
}

.tick{display:inline-flex; align-items:center; gap:5px;}
.tick::before{content:""; width:6px; height:6px; border-radius:1px; background:currentColor;}
.t-exact{color:var(--settled);}
.t-adjacent{color:var(--ink-3);}
.t-suppressed{color:var(--late);}

select.mini, input.mini{
  background-color:var(--surface); border:1px solid var(--rule-2); color:var(--ink-2);
  border-radius:var(--r-sm); font-size:12px; padding:5px 8px; cursor:pointer;
  font-family:var(--mono);
}
select.mini:focus, input.mini:focus{outline:2px solid var(--accent-soft); border-color:var(--accent-fill);}
select.mini:focus-visible, input.mini:focus-visible{outline:2px solid var(--accent); outline-offset:1px;}
/* A full 44px target that does not make the row taller. Same trick as .row .pick: the
   negative margins let the target overlap its neighbours instead of widening the cluster.
   It had shipped at 36px since the remove button was the only one here, and the edit
   button added on 13 Sep would have made a second undersized target rather than one. */
.x-btn{
  background:none; border:none; color:var(--ink-3); cursor:pointer; font-size:16px; line-height:1;
  padding:10px 9px; margin:-10px -5px;
  min-width:44px; min-height:44px;
  display:inline-flex; align-items:center; justify-content:center;
}
.x-btn:hover{color:var(--late);}
.x-btn.armed{
  color:var(--late); font-family:var(--mono); font-size:11px;
  border:1px solid var(--late); border-radius:var(--r-sm); padding:4px 7px;
}

.blank{padding:34px 0; color:var(--ink-3); font-size:14px; max-width:46ch;}
.hint{font-size:13.5px; color:var(--ink-3); margin:12px 0 0; max-width:70ch; line-height:1.55;}
.lg-group{margin-top:22px;}
.lg-group h3{
  font-family:var(--mono); font-size:10.5px; font-weight:500; letter-spacing:0.16em;
  text-transform:uppercase; color:var(--ink-3); margin:0 0 10px;
}
.lg-item{padding:13px 0; border-bottom:1px solid var(--rule);}
.lg-item:last-child{border-bottom:none;}
.lg-item .t{font-weight:700; font-size:14.5px;}
.lg-item .b{font-size:13.5px; color:var(--ink-2); margin-top:4px; max-width:74ch; line-height:1.55;}
.lg-item .s{font-family:var(--mono); font-size:11px; color:var(--ink-3); margin-top:6px;}
.bench{margin-top:8px; font-family:var(--mono); font-size:12px;}
.bench.low{color:var(--late);}
.bench.mid{color:var(--ink-2);}
.bench.high{color:var(--settled);}

/* ---------- capture sheet ---------- */
.sheet{
  background:var(--surface); border:1px solid var(--rule-2);
  border-radius:var(--r); padding:18px; margin-top:14px;
}
.sheet textarea{
  width:100%; min-height:104px; resize:vertical; background:var(--surface);
  border:1px solid var(--rule-2); border-radius:var(--r-sm); color:var(--ink);
  padding:13px 14px; font-size:14px; line-height:1.5;
}
.sheet textarea:focus{outline:2px solid var(--accent-soft); border-color:var(--accent-fill);}
.sheet textarea:focus-visible{outline:2px solid var(--accent); outline-offset:1px;}
.sheet textarea::placeholder{color:var(--ink-3);}
.sheet-actions{display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-top:12px;}
.btn{
  background:var(--accent-fill); color:var(--on-accent); border:none; border-radius:var(--r-sm);
  padding:10px 18px; font-weight:700; font-size:14px; cursor:pointer;
  min-height:var(--tap);
}
.btn:hover{background:var(--accent-hot);}
.btn:disabled{background:var(--rule-2); color:var(--ink-3); cursor:not-allowed;}
.btn.quiet{background:none; border:1px solid var(--rule-2); color:var(--ink-2); font-weight:600;}
.btn.quiet:hover{border-color:var(--ink-3); color:var(--ink); background:none;}
.read-state{font-family:var(--mono); font-size:11.5px; color:var(--ink-3);}
.read-state.on{color:var(--accent);}

.fields{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(126px, 1fr));
  gap:12px; margin-top:18px; padding-top:18px; border-top:1px solid var(--rule);
}
.fields label{display:block;}
.fields .lab{
  font-family:var(--mono); font-size:10px; letter-spacing:0.13em; text-transform:uppercase;
  color:var(--ink-3); margin-bottom:5px;
}
/* Said where the word is used. Fit, suppressed, settles on and standing were each guessed
   at by most of the people who met them, and the only definitions in the app were on other
   tabs or behind a Show button. A line under the label costs nothing and ends the guessing. */
.fields .lab-note{
  display:block; font-family:var(--sans); font-size:12.5px; line-height:1.45;
  color:var(--ink-3); margin:-1px 0 6px; text-transform:none; letter-spacing:0;
}
.fields input[type=file]{font-size:12px; padding:7px 8px; color:var(--ink-2);}
.fields input, .fields select{
  width:100%; background-color:var(--surface); border:1px solid var(--rule-2); color:var(--ink);
  border-radius:var(--r-sm); padding:9px 10px; font-size:14px;
}
.fields input:focus, .fields select:focus{outline:2px solid var(--accent-soft); border-color:var(--accent-fill);}
/* The soft token is E1EDE6 and measures 1.2:1 on white, against a 3:1 standard for a
   focus indicator, so five identical dropdowns down the campaign list all looked the
   same when one of them held focus. Keyboard focus takes the full ivy ring that every
   button already uses. The soft ring stays for the pointer case, where it reads as a
   hover-weight tint rather than as a position. */
.fields input:focus-visible, .fields select:focus-visible{outline:2px solid var(--accent); outline-offset:1px;}
.field-note{font-family:var(--mono); font-size:11px; margin-top:6px; min-height:15px; color:var(--ink-3);}
.field-note.suppressed{color:var(--late);}
.field-note.exact{color:var(--settled);}
.field-note.adjacent{color:var(--late);}

.rate-call{
  margin-top:16px; padding:13px 15px; border-radius:var(--r-sm);
  background:var(--surface); border:1px solid var(--rule-2);
  font-family:var(--mono); font-size:12.5px; color:var(--ink-2);
}
.rate-call b{color:var(--accent); font-weight:600;}

/* ---------- goal ---------- */
.goal-set{display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap; margin-top:16px;}
.goal-set input{
  width:130px; background:var(--surface); border:1px solid var(--rule-2); color:var(--ink);
  border-radius:var(--r-sm); padding:9px 11px; font-size:16px; font-family:var(--mono);
}
.track{height:8px; background:var(--rule); border-radius:999px; overflow:hidden; margin-top:20px;}
.track i{display:block; height:100%; background:var(--accent-hot); border-radius:999px; transition:width .4s ease;}

/* ---------- editor tiles ---------- */
.slots{display:grid; grid-template-columns:repeat(auto-fit, minmax(150px,1fr)); gap:12px; margin-top:16px;}
.slot{border:1px solid var(--rule-2); border-radius:var(--r); padding:16px;}
.slot .n{font-family:var(--mono); font-size:26px; font-weight:600;}
.slot .cap{font-size:12.5px; color:var(--ink-3); margin-top:2px;}
.slot .left{font-family:var(--mono); font-size:11.5px; color:var(--ink-2); margin:12px 0;}
.slot button{width:100%;}

/* ---------- auth, direction A "ivy field", 17 Sep 2026 (Kira direction, Maya build) ----------
   The gate doubles as the launch screen, so it is one full bleed ivy field from the first
   paint, with the mark drawn large. Launch shows the mark alone. The words and controls
   keep their space while hidden, so the mark does not move when they arrive. */
.gate{
  min-height:100vh; min-height:100dvh; background:var(--accent); color:var(--on-accent);
  display:flex; flex-direction:column; align-items:center; text-align:center;
  padding:0 var(--gut) calc(24px + env(safe-area-inset-bottom, 0px));
}
.gate-brand{flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; padding-top:8vh;}
.gate-mark{display:block; width:clamp(104px, 32vw, 136px); height:auto;}
.gate-name{font-family:var(--display); font-size:44px; font-weight:700; letter-spacing:-0.02em; line-height:1; margin:18px 0 10px;}
.gate-line{font-size:15px; color:var(--on-accent-2); margin:0;}
.gate-act{width:100%; max-width:360px; display:flex; flex-direction:column; gap:12px;
  transition:opacity .32s cubic-bezier(.22,.61,.36,1) .08s, transform .32s cubic-bezier(.22,.61,.36,1) .08s;}
.gate-name,.gate-line{transition:opacity .24s ease-out;}
.gate[data-stage="launch"] .gate-name,.gate[data-stage="launch"] .gate-line,
.gate[data-stage="launch"] .gate-act{opacity:0; visibility:hidden;}
.gate[data-stage="launch"] .gate-act{transform:translateY(10px);}
.gate-mail{display:flex; flex-direction:column; gap:10px;}
.gate[data-way="google"] .gate-mail,.gate[data-way="email"] .gate-google{display:none;}
/* Once a code is on its way the send button gives way to the code box, so the app never
   needs the emailed link (17 Sep 2026, Chris: the link opened a browser, not the app). */
.gate:not([data-code]) .gate-code,.gate[data-code] #gate-send{display:none;}
.gate input.gate-code{text-align:center; font-family:var(--mono); letter-spacing:.2em;}
.gate input.gate-code::placeholder{font-family:var(--sans); letter-spacing:0;}
.gate-google,.gate-send,.gate input{width:100%; min-height:52px; border-radius:26px; font-family:var(--sans); font-size:15px;}
.gate-google,.gate-send{
  display:flex; align-items:center; justify-content:center; gap:10px;
  border:none; background:var(--surface); color:var(--ink); font-weight:600; cursor:pointer;
}
.gate-google:hover,.gate-send:hover{background:var(--accent-soft);}
.gate-google:disabled,.gate-send:disabled{cursor:progress;}
.gate input{background:var(--surface); border:none; color:var(--ink); padding:0 20px;}
.gate input:focus{outline:none;}
.gate input:focus-visible,.gate button:focus-visible,.gate a:focus-visible{outline:2px solid var(--on-accent); outline-offset:3px;}
.gate-alt{background:none; border:none; color:var(--on-accent); font-family:var(--sans); font-size:14px; font-weight:600; min-height:var(--tap); cursor:pointer;}
.gate .gate-msg{margin:0; padding:12px 16px; border-radius:16px; background:var(--accent-fill); font-size:14px; line-height:1.5;}
.gate .gate-msg.ok,.gate .gate-msg.err{color:var(--on-accent);}
.gate-fine{font-size:12.5px; color:var(--on-accent-3); margin:4px 0 0;}
.gate-links{display:flex; justify-content:center; gap:18px; font-size:12.5px;}
.gate-links a{display:inline-flex; align-items:center; min-height:var(--tap); padding:0 4px; color:var(--on-accent-3); text-decoration:underline; text-underline-offset:3px;}
.gate-links a:hover{color:var(--on-accent);}
/* ---------- editorial pass, 2026-09-01 (Kira direction, Maya build) ----------
   Mono is for numbers, eyebrows and source citations only. Everything a person reads
   as a sentence is set in the sans. A hidden element is hidden whatever its display
   rule says. Zero is never set in the accent. */
[hidden]{display:none !important;}
.tape-num.zero{color:var(--ink-3);}
.tape.first .tape-num{
  font-family:var(--sans); font-size:clamp(26px, 5vw, 34px); font-weight:800;
  color:var(--ink); letter-spacing:-0.025em; line-height:1.15; max-width:24ch; margin-top:10px;
}
.tape-unit{font-family:var(--sans); font-size:14.5px; color:var(--ink-2); margin-top:10px; max-width:52ch; line-height:1.45;}
.split-key{font-family:var(--sans); font-size:12.5px;}
.sec-head .note{font-family:var(--sans); font-size:12.5px;}
.read-state{font-family:var(--sans); font-size:12.5px;}
.row .meta{font-family:var(--sans); font-size:12.5px; gap:10px 14px;}
.row .meta b{font-weight:600;}
.row.season .fig{font-family:var(--sans); font-size:13.5px; font-weight:600;}
.slot .left{font-family:var(--sans); font-size:12.5px;}
.rate-call{font-family:var(--sans); font-size:13.5px;}
.field-note{font-family:var(--sans); font-size:12.5px;}
.blank{font-size:14.5px; line-height:1.55;}
/* Counts are data, so they are set like data. Tabular figures stop the rail jumping
   sideways when 11 becomes 9, and the quiet state is the default because most counts are
   information rather than a demand. Ivy is reserved for a number that wants something. */
nav.rail .count{
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:10.5px; font-weight:500; line-height:16px;
  color:var(--ink-3); background:var(--surface-2);
  border:1px solid var(--rule); border-radius:999px; padding:0 6px;
  transition:background-color .15s ease, color .15s ease, border-color .15s ease;
}
nav.rail .count.hot{color:var(--on-accent); background:var(--accent-fill); border-color:var(--accent-fill);}
@media (prefers-reduced-motion:reduce){
  nav.rail button, nav.rail button::after, nav.rail .count{transition:none;}
}
/* The capture sheet opens as one box and one button. The fields arrive when a post
   has been read in, when a market campaign is taken, or when the creator asks for them. */
.sheet.folded .fields, .sheet.folded .field-note, .sheet.folded .rate-call{display:none;}
.hand-btn{
  background:none; border:none; cursor:pointer; font-size:13px; color:var(--ink-3);
  text-decoration:underline; text-underline-offset:3px;
  padding:9px 4px; margin:-9px -4px;
}
.hand-btn:hover{color:var(--ink);}
.sheet:not(.folded) .hand-btn{display:none;}

/* A trend item that has a post behind it says so. The triangle is drawn rather than
   loaded, and it only appears where there is actually something to watch. */
.watch{color:inherit; text-decoration:none; display:inline-flex; align-items:center; gap:7px;}
.watch:hover{text-decoration:underline; text-underline-offset:3px;}
.watch .play{
  width:0; height:0; flex:none; margin-top:1px;
  border-left:9px solid var(--accent); border-top:6px solid transparent; border-bottom:6px solid transparent;
}
.watch:hover .play{border-left-color:var(--accent-hot);}

/* Import. The preview is the safety feature: nothing is written until the creator has
   seen which column we read as what, and a few real rows. */
.drop{display:flex; align-items:center; gap:12px; flex-wrap:wrap;}
.map{margin-top:16px; border-top:1px solid var(--rule); padding-top:14px;}
.map h4{font-family:var(--mono); font-size:10.5px; letter-spacing:0.13em; text-transform:uppercase;
  color:var(--ink-3); margin:0 0 9px; font-weight:500;}
.map-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:8px 16px;}
.map-row{display:flex; justify-content:space-between; gap:10px; font-size:13px; padding:5px 0;
  border-bottom:1px solid var(--rule);}
.map-row .k{color:var(--ink-2);}
.map-row .v{font-family:var(--mono); font-size:11.5px; text-align:right; color:var(--ink);}
.map-row .v.none{color:var(--ink-3);}
.prev{margin-top:14px; overflow-x:auto;}
.prev table{border-collapse:collapse; font-size:12px; white-space:nowrap;}
.prev th{font-family:var(--mono); font-size:10px; letter-spacing:0.09em; text-transform:uppercase;
  color:var(--ink-3); text-align:left; padding:0 14px 6px 0; font-weight:500;}
.prev td{padding:6px 14px 6px 0; border-top:1px solid var(--rule); color:var(--ink-2);}
.prev td b{color:var(--ink); font-weight:600;}

/* Niche toggles are form controls, not decoration: square-cornered, one weight
   change when pressed, no colour fill. */
.toggles{display:flex; flex-wrap:wrap; gap:8px; margin-top:14px;}
.toggle{
  background:var(--surface); border:1px solid var(--rule-2); border-radius:var(--r-sm);
  padding:8px 12px; font-size:13.5px; color:var(--ink-2); cursor:pointer; font-weight:500;
  /* Every one of these writes to the profile and re-renders five surfaces, so they are the
     highest-consequence controls on the page and they shipped at 34px. */
  min-height:var(--tap); display:inline-flex; align-items:center;
}
.toggle:hover{border-color:var(--ink-3); color:var(--ink);}
.toggle[aria-pressed="true"]{border-color:var(--ink); color:var(--ink); font-weight:700; background:var(--surface-2);}
.niche-line{margin:14px 0 -14px; font-size:12.5px;}
/* The padding grows the tap area to 44 and the equal negative margin keeps the line where it was. */
.niche-line .hand-btn{font-size:12.5px; padding:15px 4px; margin:-15px -4px -15px 6px;}
.row .fig.word{font-family:var(--sans); font-size:13.5px; font-weight:600;}
.row .fig.ok{color:var(--settled);}
.row .fig.quiet{color:var(--ink-3);}
.row .fig.warn{color:var(--late);}

/* ---------- tiktok ---------- */
.tt-card{display:flex; align-items:center; gap:14px; padding:12px 0 14px; flex-wrap:wrap;}
.tt-avatar{width:48px; height:48px; border-radius:50%; object-fit:cover; background:var(--surface); flex:none;}
.tt-id{flex:1; min-width:160px;}
.tt-name{font-weight:700; font-size:16px; letter-spacing:-0.01em;}
.tt-handle{font-family:var(--mono); font-size:12.5px; color:var(--ink-3); text-decoration:none;}
.tt-handle:hover{color:var(--ink); text-decoration:underline;}
.tt-actions{display:flex; gap:10px; align-items:center;}
.tt-stats{display:grid; grid-template-columns:repeat(auto-fit, minmax(120px, 1fr)); gap:1px; background:var(--rule); border:1px solid var(--rule); border-radius:var(--r-sm); overflow:hidden; margin:0 0 10px;}
.tt-stats div{background:var(--surface); padding:12px 14px;}
.tt-stats .num{display:block; font-size:22px; font-weight:800; letter-spacing:-0.02em;}
.tt-stats .lab{font-size:12px; color:var(--ink-3);}
.tt-list{margin-top:6px;}
.tt-video{display:flex; gap:12px; align-items:center; padding:10px 0; border-bottom:1px solid var(--rule);}
.tt-video img{width:40px; height:54px; object-fit:cover; border-radius:6px; background:var(--surface); flex:none;}
.tt-video .t{flex:1; min-width:0; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.tt-video .t a{color:inherit; text-decoration:none;}
.tt-video .t a:hover{text-decoration:underline;}
.tt-video .d{font-family:var(--mono); font-size:11.5px; color:var(--ink-3); margin-top:2px;}
.tt-video .v{font-family:var(--mono); font-size:12.5px; white-space:nowrap; text-align:right;}
.foot-links{display:block; margin-top:10px;}
.foot-links a{color:inherit; text-decoration:underline; text-underline-offset:3px; margin-right:16px;}
.gate-msg{font-family:var(--sans); font-size:13.5px; margin-top:14px; display:none;}
.gate-msg.on{display:block;}
.gate-msg.ok{color:var(--settled);}
.gate-msg.err{color:var(--late);}

/* ---------- toast ---------- */
/* ---------- loading ----------
   The app shell appears the moment someone signs in and then sixteen queries run before
   anything renders. That gap was blank, and a blank screen on a phone over patchy signal
   does not read as loading, it reads as broken. Two signals: a bar that says work is
   happening, and skeleton rows that say what shape is coming. */
.loadbar{
  position:fixed; top:0; left:0; right:0; height:2px; z-index:90;
  background:var(--accent-soft); overflow:hidden; opacity:0;
  transition:opacity .2s ease; pointer-events:none;
}
.loadbar.on{opacity:1;}
/* The lead line and the tape numeral ship with a placeholder in the markup and are only
   true once the queries return. Held back while the app is busy, which is the same promise
   the list skeletons already make one section lower. loading() sets aria-busy on #app and
   clears it in both the success and the failure branch, so this needs no JS. */
#app[aria-busy="true"] #lead-line,#app[aria-busy="true"] #lead-why,
#app[aria-busy="true"] #hero,#app[aria-busy="true"] #hero-read{visibility:hidden;}
.loadbar i{
  display:block; height:100%; width:36%; background:var(--accent-hot);
  transform:translateX(-100%); animation:slide 1.15s ease-in-out infinite;
}
@keyframes slide{
  0%{transform:translateX(-100%);} 100%{transform:translateX(320%);}
}

li.skel{
  list-style:none; padding:15px 0; border-bottom:1px solid var(--rule);
  display:grid; grid-template-columns:38px 1fr auto; gap:12px; align-items:center;
}
li.skel span{
  display:block; height:11px; border-radius:3px;
  background:linear-gradient(90deg, var(--surface-2) 0%, var(--rule) 50%, var(--surface-2) 100%);
  background-size:200% 100%; animation:sheen 1.4s ease-in-out infinite;
}
li.skel .a{width:38px; height:38px; border-radius:9px;}
li.skel .b{width:min(58%, 260px);}
li.skel .c{width:54px;}
@keyframes sheen{0%{background-position:200% 0;} 100%{background-position:-200% 0;}}

/* ---------- offline ----------
   She will use this on a train. A write that fails with no explanation loses her entry and
   she finds out later, at which point she stops trusting the ledger. Saying it plainly and
   early is cheaper than any amount of retry logic. */
.offbar{
  position:fixed; left:0; right:0; top:0; z-index:88;
  background:var(--late); color:#FFFFFF;
  font-size:13px; font-weight:600; text-align:center;
  padding:9px 16px; transform:translateY(-100%);
  transition:transform .22s ease;
}
.offbar.on{transform:translateY(0);}
/* The strip is reserved rather than overlaid. Without this the bar covered the header
   and, being the higher layer, swallowed every click on the five tabs and the New
   button. Keyboard still worked, which is how the fault hid. */
html.has-offbar body{padding-top:var(--offbar-h);}
html.has-offbar .topbar{top:var(--offbar-h);}

/* The toast grew an action, so it is no longer only a line of text. */
/* The toast carries the only Undo in the app, and at bottom:26px it sat inside the home
   indicator strip on a notched iPhone, where the first touch belongs to the system swipe.
   She tapped Undo, nothing happened, the toast timed out and the record was gone. */
.toast{
  position:fixed; left:50%; bottom:calc(26px + env(safe-area-inset-bottom, 0px));
  transform:translate(-50%, 12px);
  background:#14171C; border:1px solid #14171C; color:#FFFFFF;
  padding:11px 18px; border-radius:999px; font-size:13.5px;
  opacity:0; pointer-events:none; transition:opacity .18s ease, transform .18s ease; z-index:80;
}
.toast.on{opacity:1; transform:translate(-50%, 0); pointer-events:auto;}
.toast .undo{
  background:none; border:none; color:#8FD6AC; font-family:var(--sans);
  font-size:13.5px; font-weight:700; cursor:pointer; padding:0 0 0 14px; margin-left:2px;
  border-left:1px solid rgba(255,255,255,.22);
}
.toast .undo:hover{color:#FFFFFF;}
@media (prefers-reduced-motion:reduce){
  .loadbar i, li.skel span{animation:none;}
  .offbar, .toast{transition:none;}
}

footer.foot{
  margin-top:56px; padding-top:20px; border-top:1px solid var(--rule);
  font-size:12px; color:var(--ink-3); line-height:1.7; max-width:70ch;
}

/* ---------- touch ----------
   iOS Safari zooms the entire page when you focus an input whose font size is under 16px.
   Every text surface in this app was between 12 and 15, so the sign-in field, the campaign
   paste box and every form would have jumped and zoomed under Jen's thumb, starting with
   the very first thing she taps. Desktop keeps the smaller scale it was designed at.

   pointer:coarse rather than a width query, because it is the input device that decides
   this, not the screen. An iPad in landscape is wide and still a thumb. */
@media (pointer: coarse){
  .gate input,
  .fields input, .fields select, .fields textarea,
  .sheet textarea,
  select.mini, input.mini,
  input[type=text], input[type=email], input[type=number], input[type=date], input[type=url],
  input[type=search], .search input,
  select, textarea{
    font-size:16px;
  }
  /* The mini controls carry mono at a deliberately small size. Sixteen is the floor, so
     they get their proportions back from padding rather than from type. */
  select.mini, input.mini{padding:7px 10px;}
}

/* ---------- mobile ---------- */
@media (max-width:760px){
  nav.rail{
    position:fixed; top:auto; bottom:0; left:0; right:0;
    border-top:1px solid var(--rule); border-bottom:none;
    padding:0 4px calc(env(safe-area-inset-bottom, 0px)); background:var(--surface);
    justify-content:space-between;
  }
  nav.rail button{
    flex:1; flex-direction:column; gap:3px; justify-content:center;
    padding:8px 2px 9px; margin:0; font-size:11px; font-weight:600;
    min-height:var(--tap);
  }
  /* On a bottom bar the indicator belongs at the top edge, where it sits against the
     content rather than against the bottom of the screen. */
  nav.rail button::after{top:0; bottom:auto; border-radius:0 0 2px 2px;}
  nav.rail button:hover::after{transform:scaleX(0);}
  nav.rail button[aria-current="page"]::after{transform:scaleX(1);}
  nav.rail .count{font-size:10px; line-height:15px; padding:0 5px;}
  .wrap{padding-bottom:104px;}
  .who .mail{display:none;}
  .row .money{font-size:17px;}
  .row{grid-template-columns:34px 1fr auto;}
  .row .tile{width:34px; height:34px; border-radius:9px;}
  .row .tile i{font-size:11.5px;}
  .row .meta{grid-column:2 / -1;}
  /* Spans from column 1, not 2, and wraps. The cluster gained the edit button on 13 Sep
     and at 390px the two x-btns sat 25px past the viewport. Starting at the tile column
     recovers 34px plus the gap, and the wrap covers whatever the next control costs. */
  .row .side{grid-column:1 / -1; grid-row:3; justify-content:flex-start; margin-top:6px;
    flex-wrap:wrap; row-gap:6px;}
  /* One bare remove button does not need a row of its own. Only the multi-button clusters
     did, and putting every .side on row 3 gave the tier list eight orphan crosses at 390px. */
  #brand-rows .row .side{grid-column:3; grid-row:1; justify-content:flex-end; margin-top:0;}
  /* .side takes row 3 at this width, so the pasted offer moves down one and the amendment
     trail moves down with it. Placed here rather than beside their own rules so the
     conflict is visible from the line that causes it. */
  .row .orig{grid-row:4;}
  .row .trail{grid-row:5;}
  .row.plain .orig{grid-column:1 / -1; grid-row:4;}
  .row.plain{grid-template-columns:1fr auto;}
  .row.plain .meta{grid-column:1 / -1;}
  .row.plain .side{grid-column:1 / -1; grid-row:3;}
  .row.has-shot{grid-template-columns:34px 1fr auto;}
  .shot{width:34px; height:46px; border-radius:8px;}
  .row .fig{font-size:15px;}
}

/* ============================================================
   App frame, 2026-09-05.
   The first build put every form inline behind an "Open" button and stacked the tabs
   as bare words under the wordmark. It read as a document with controls in it. This
   pass gives it the frame of an app: one bar with the sections in it, an account
   menu, a single "New" action, forms that open as sheets over the page and close
   behind you, and a desk that leads with what needs you rather than with prose.
   Nothing about the type, the palette or the ruled rows changes.
   ============================================================ */

/* ---------- top bar ---------- */
.topbar{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:24px;
  padding:0 var(--gut); height:var(--bar-h);
}
.topbar .lockup{gap:8px;}
.topbar .wordmark{font-size:19px;}
.who{gap:10px;}
.btn.new{
  display:inline-flex; align-items:center; gap:7px; padding:0 14px 0 11px; min-height:38px;
  font-size:13.5px; border-radius:999px;
}
.btn.new svg{width:15px; height:15px;}
.avatar{
  width:36px; height:36px; border-radius:50%; border:1px solid var(--rule-2);
  background:var(--accent-soft); color:var(--accent); font-weight:800; font-size:14px;
  display:grid; place-items:center; cursor:pointer; padding:0; text-transform:uppercase;
  font-family:var(--sans);
}
.avatar:hover, .avatar[aria-expanded="true"]{border-color:var(--accent);}
.who{position:relative;}
.menu{
  position:absolute; right:0; top:calc(100% + 8px); min-width:230px; z-index:60;
  background:var(--surface); border:1px solid var(--rule-2); border-radius:var(--r);
  box-shadow:0 12px 32px rgba(18,23,17,.12); padding:6px; text-align:left;
}
.menu-mail{
  font-size:12.5px; color:var(--ink-3); padding:8px 10px 10px; border-bottom:1px solid var(--rule);
  margin-bottom:4px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.menu button{
  display:block; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  padding:10px 10px; border-radius:var(--r-sm); font-size:14px; color:var(--ink); min-height:40px;
}
.menu button:hover{background:var(--surface-2);}

/* ---------- section rail, desktop ---------- */
@media (min-width:761px){
  nav.rail{
    position:static; border:none; padding:0; gap:2px; justify-self:center;
    height:var(--bar-h); align-items:stretch; overflow:visible;
  }
  nav.rail button{padding:0 12px; margin:0; font-size:14px;}
  nav.rail button::after{bottom:0; left:12px; right:12px;}
  nav.rail button svg{width:17px; height:17px; flex:none; opacity:.75;}
  nav.rail button[aria-current="page"] svg{opacity:1; color:var(--accent);}
}
nav.rail button svg{fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round;}
.wrap{max-width:1080px;}

/* ---------- section rail, phone ---------- */
@media (max-width:760px){
  .topbar{grid-template-columns:auto 1fr auto; height:56px;}
  nav.rail{grid-column:1 / -1;}
  nav.rail button{gap:4px; padding:7px 2px 6px;}
  nav.rail button svg{width:22px; height:22px;}
  nav.rail button[aria-current="page"] svg{color:var(--accent);}
  nav.rail .count{position:absolute; top:5px; left:calc(50% + 6px);}
  .btn.new{display:none;}
}
.fab{
  display:none; position:fixed; right:18px; bottom:calc(72px + env(safe-area-inset-bottom, 0px)); z-index:70;
  width:54px; height:54px; border-radius:50%; border:none; cursor:pointer;
  background:var(--accent-fill); color:var(--on-accent); place-items:center;
  box-shadow:0 10px 26px rgba(15,81,50,.32);
}
.fab:hover{background:var(--accent-hot);}
.fab svg{width:24px; height:24px; fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round;}
@media (max-width:760px){ .fab{display:grid;} }
/* The + lives outside #app, so on a phone it floated over the sign in field. */
.gate:not([hidden]) ~ .fab{display:none;}
/* Four reviewers reported the + sitting on top of something they were trying to read: the
   third setup step, a sample's day count, an offer description. It is fixed, so whatever
   scrolls under it collides at some point. Fading it while she scrolls hands the text back
   without taking the control away, and it returns the moment she stops. */
.fab{transition:opacity .18s ease, transform .18s ease;}
.fab.dim{opacity:.22; transform:scale(.94);}
.fab:focus-visible, .fab:active{opacity:1; transform:none;}
@media (prefers-reduced-motion: reduce){ .fab{transition:none;} }

/* ---------- sheets ---------- */
/* One dialog element hosts whichever form was asked for. The form's own markup and ids
   stay exactly as they were, so every handler keeps working. It is moved in, shown, and
   moved back to its hidden home on close. Native dialog gives the focus trap, Escape and
   the backdrop for free, and iOS has supported it since 15.4. */
.dlg{
  border:none; padding:0; background:var(--surface); color:var(--ink);
  border-radius:14px; width:min(660px, calc(100vw - 32px)); max-width:none;
  max-height:min(88vh, 900px); box-shadow:0 24px 60px rgba(18,23,17,.2); overflow:auto;
}
.dlg::backdrop{background:rgba(18,23,17,.44);}
.dlg-head{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:16px 20px 12px; border-bottom:1px solid var(--rule);
  position:sticky; top:0; background:var(--surface); z-index:1;
}
.dlg-head h2{font-family:var(--display); font-size:20px; font-weight:700; margin:0; letter-spacing:-0.01em;}
.dlg-x{font-size:22px; margin:0; padding:6px 8px; color:var(--ink-3);}
.dlg-x:hover{color:var(--ink);}
.dlg-body{padding:16px 20px 22px;}
.dlg-body .sheet{border:none; padding:0; margin:0; background:none;}
.dlg-body .sheet .fields{margin-top:14px;}
.dlg-body .sheet .hint{margin-top:8px;}
.dlg-body .gate-msg{margin-top:12px;}
.dlg-note{font-size:12.5px; color:var(--ink-3); margin:0 0 10px;}
.dlg-note b{color:var(--accent); font-weight:600;}
.dlg-note .chip{
  display:inline-block; margin-left:6px; font-family:var(--mono); font-size:10.5px;
  letter-spacing:0.04em; border:1px solid var(--rule-2); border-radius:999px; padding:1px 8px;
  vertical-align:1px;
}
.dlg-note .chip:empty{display:none;}
@media (max-width:760px){
  .dlg{
    /* vh on iOS measures the LARGE viewport, the one with Safari's toolbars retracted, so
       92vh is more than the whole visible area while they are showing. The sheet is
       bottom anchored, so the excess came off the top and took the title and the close
       button with it. svh is the small viewport, which is what she can actually see. */
    width:100vw; margin:auto 0 0; border-radius:16px 16px 0 0;
    max-height:92vh; max-height:92svh;
    overscroll-behavior:contain;
    padding-bottom:env(safe-area-inset-bottom, 0px);
  }
}
/* The "New" chooser. Four doors, each named for the thing rather than the form. */
.choices{display:grid; gap:8px; padding-top:8px;}
.choices button{
  display:grid; grid-template-columns:1fr auto; gap:2px 12px; text-align:left;
  background:var(--surface); border:1px solid var(--rule-2); border-radius:var(--r);
  padding:13px 15px; cursor:pointer; min-height:var(--tap); font-family:var(--sans);
}
.choices button:hover{border-color:var(--ink-3); background:var(--ground);}
.choices b{font-size:15px; font-weight:700; color:var(--ink);}
.choices span{grid-column:1; font-size:13px; color:var(--ink-3);}
.choices i{grid-column:2; grid-row:1 / span 2; align-self:center; color:var(--ink-3); font-style:normal;}

/* ---------- the desk ---------- */
.cols{display:grid; grid-template-columns:1fr; gap:0 48px; align-items:start;}
@media (min-width:900px){
  .cols{grid-template-columns:minmax(0, 1fr) 300px;}
  .side-col{position:sticky; top:calc(var(--bar-h) + 24px);}
  .side-col section:first-child{margin-top:38px;}
}
/* One column with the number beside its label is the readable layout, so it is the
   default. Three across was the base and the fix lived behind min-width:900px, which is
   the one place Jen never reaches: at 393px each tile had 83px of content, a six figure
   sum painted past its own border, and "campaigns open on the market" wrapped to six
   lines. The three-across grid now starts at the tablet width where it fits. */
.stats{display:grid; grid-template-columns:1fr; gap:8px; margin-top:38px;}
@media (min-width:620px) and (max-width:899px){
  .stats{grid-template-columns:repeat(3, 1fr); gap:10px;}
}
.stat{
  display:block; text-align:left; background:var(--surface); border:1px solid var(--rule-2);
  border-radius:var(--r); padding:14px 14px 12px; cursor:pointer; font-family:var(--sans);
  min-height:var(--tap); transition:border-color .15s ease;
}
.stat:hover{border-color:var(--ink-3);}
.stat .n{
  display:block; font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:24px; font-weight:600; letter-spacing:-0.02em; color:var(--ink); line-height:1.1;
}
.stat .n.hot{color:var(--accent);}
.stat .n.quiet{color:var(--ink-3);}
.stat .l{display:block; font-size:12.5px; color:var(--ink-3); margin-top:6px; line-height:1.35;}
/* The horizontal tile is now the default rather than a wide-screen upgrade. */
.stat{display:grid; grid-template-columns:auto 1fr; gap:12px; align-items:center; padding:12px 14px;}
.stat .n{font-size:22px; min-width:52px;}
.stat .l{margin-top:0;}
@media (min-width:620px) and (max-width:899px){
  .stat{display:block; padding:14px 14px 12px;}
  .stat .n{font-size:24px; min-width:0;}
  .stat .l{margin-top:6px;}
}
.card{background:var(--surface); border:1px solid var(--rule-2); border-radius:var(--r); padding:18px 18px 14px;}
.card .sec-head{padding-bottom:10px; border-bottom-color:var(--rule);}

/* Setup. Three steps, each a fact about the account rather than a claim about the
   product. It removes itself when all three are true. */
.steps{list-style:none; margin:0; padding:0;}
/* The four TikTok navigation steps ran together inside a paragraph, which a phone rendered
   as eleven lines with a creator switching apps between each one. */
.steps-num{margin:10px 0 0; padding-left:20px; color:var(--ink-2); font-size:14px; line-height:1.9; max-width:70ch;}
.steps-num li::marker{font-family:var(--mono); font-size:12px; color:var(--ink-3);}
.step{
  display:grid; grid-template-columns:22px 1fr auto; gap:2px 12px; align-items:center;
  padding:12px 0; border-bottom:1px solid var(--rule);
}
.step:last-child{border-bottom:none;}
.step .mark{
  grid-row:1 / span 2; width:20px; height:20px; border-radius:50%; border:1.5px solid var(--rule-2);
  display:grid; place-items:center; align-self:start; margin-top:1px;
}
.step .mark svg{width:11px; height:11px; fill:none; stroke:var(--on-accent); stroke-width:2.5; stroke-linecap:round; stroke-linejoin:round; display:none;}
.step.done .mark{background:var(--accent); border-color:var(--accent);}
.step.done .mark svg{display:block;}
.step .t{font-weight:600; font-size:14.5px;}
.step.done .t{color:var(--ink-3); text-decoration:line-through; text-decoration-color:var(--rule-2);}
.step .d{grid-column:2; font-size:12.5px; color:var(--ink-3); line-height:1.4;}
.step .btn{grid-column:3; grid-row:1 / span 2; padding:8px 12px; font-size:13px; min-height:38px;}
.step.done .btn{display:none;}

/* Needs you. The strips that used to sit under the tape became rows in a list, so an
   empty day is one calm line and a busy one is a list you can work down. */
.row .fig.warn{color:var(--late);}
.row .fig.bad{color:var(--lost);}
.blank{display:flex; flex-direction:column; gap:12px; align-items:flex-start; padding:22px 0 26px; max-width:none;}
.blank p{margin:0; max-width:52ch;}
.blank.calm{color:var(--settled); padding:18px 0;}
.blank .acts{display:flex; gap:8px; flex-wrap:wrap;}
/* 44 by padding, not by type, WO-14: these were the first taps a new creator makes, at 38. */
.blank .btn{padding:11px 13px; font-size:13px; min-height:var(--tap);}

/* ---------- rows ---------- */
.row{padding:15px 0; transition:background-color .12s ease;}
.rows .row{margin:0 calc(var(--gut) * -0.5); padding-left:calc(var(--gut) * 0.5); padding-right:calc(var(--gut) * 0.5); border-radius:var(--r-sm);}
.rows .row:hover{background:rgba(255,255,255,.55);}
.pill{
  position:relative; display:inline-flex; align-items:center;
}
.pill::before{
  content:""; position:absolute; left:9px; top:50%; width:7px; height:7px; border-radius:50%;
  transform:translateY(-50%); background:var(--rule-2); pointer-events:none;
}
.pill[data-st="claimed"]::before, .pill[data-st="contacted"]::before, .pill[data-st="sample_requested"]::before{background:var(--late);}
.pill[data-st="submitted"]::before{background:var(--accent-hot);}
.pill[data-st="paid"]::before, .pill[data-st="deal"]::before, .pill[data-st="sample_approved"]::before{background:var(--settled);}
.pill[data-st="declined"]::before{background:var(--lost);}
.pill select.mini{padding-left:22px; padding-right:30px; border-radius:999px; font-family:var(--sans); font-weight:600; max-width:100%;}
.pill select.mini:hover{border-color:var(--ink-3); color:var(--ink);}

/* ---------- search ---------- */
.search{
  display:flex; align-items:center; gap:8px; margin-top:14px;
  background:var(--surface); border:1px solid var(--rule-2); border-radius:999px; padding:0 14px;
  min-height:var(--tap);
}
.search svg{width:16px; height:16px; flex:none; fill:none; stroke:var(--ink-3); stroke-width:1.8; stroke-linecap:round;}
.search input{
  flex:1; min-width:0; border:none; background:none; font-size:14px; padding:10px 0; color:var(--ink);
}
.search input:focus{outline:none;}
.search:focus-within{outline:2px solid var(--accent); outline-offset:1px;}
.search input::placeholder{color:var(--ink-3);}

.listbar{display:flex; flex-wrap:wrap; gap:8px 12px; align-items:center; margin-top:12px;}
.chips{display:flex; flex-wrap:wrap; gap:7px; flex:1 1 auto; min-width:0;}
.chip{
  position:relative; display:inline-flex; align-items:center; gap:7px;
  min-height:var(--tap); padding:0 14px 0 25px; border-radius:999px;
  border:1px solid var(--rule-2); background:var(--surface);
  font:inherit; font-size:13.5px; color:var(--ink-2); cursor:pointer; white-space:nowrap;
}
.chip::before{
  content:""; position:absolute; left:11px; top:50%; width:7px; height:7px; border-radius:50%;
  transform:translateY(-50%); background:var(--rule-2); pointer-events:none;
}
.chip[data-st="claimed"]::before{background:var(--late);}
.chip[data-st="submitted"]::before{background:var(--accent-hot);}
.chip[data-st="paid"]::before{background:var(--settled);}
.chip[data-st="all"]{padding-left:14px;}
.chip[data-st="all"]::before{content:none;}
.chip:hover{border-color:var(--ink-3); color:var(--ink);}
.chip:focus-visible{outline:2px solid var(--accent); outline-offset:2px;}
.chip[aria-pressed="true"]{border-color:var(--accent); background:var(--accent-soft); color:var(--accent); font-weight:600;}
.chip .n{font-family:var(--mono); font-variant-numeric:tabular-nums; font-size:12.5px;}
.sortby{display:flex; align-items:center; flex:0 0 auto;}
/* select.mini renders at 26px, which is well under the 44px tap minimum the brand kit
   sets. The 2026-09-11 audit already counted thirteen controls in that band and this is a
   new one, so it does not get to join them. Scoped to .sortby rather than fixing .mini
   globally, because .mini inside a row sits in a dense grid and raising it there is a
   layout change that wants its own pass. */
.sortby select.mini{min-height:var(--tap); padding-top:0; padding-bottom:0;}
@media (max-width:520px){ .sortby{flex:1 1 100%;} .sortby select{width:100%;} }

.search:focus-within{border-color:var(--accent-fill); box-shadow:0 0 0 2px var(--accent-soft);}

/* ---------- settings, account ---------- */
.acct{display:flex; align-items:center; gap:14px; padding:16px 0 4px; flex-wrap:wrap;}
.acct .avatar{width:44px; height:44px; font-size:17px; cursor:default;}
.acct .m{flex:1; min-width:180px;}
.acct .m b{display:block; font-size:15px; font-weight:700; word-break:break-all;}
.acct .m span{display:block; font-size:12.5px; color:var(--ink-3); margin-top:2px;}
.settings-tape{padding-top:30px;}

/* ---------- forms in sheets ----------
   Two columns on a phone, three on a desktop, the brand and the button always full
   width, and the fields that are rarely filled in behind "More details". A form that
   shows eight boxes for a deal that has three facts is the single biggest thing that
   made the app read as a spreadsheet. */
.fields{grid-template-columns:repeat(2, minmax(0, 1fr));}
@media (min-width:761px){ .fields{grid-template-columns:repeat(3, minmax(0, 1fr));} }
.fields .wide{grid-column:1 / -1;}
.fields .submit{margin-top:4px;}
.fields .lab{font-family:var(--sans); font-size:12px; letter-spacing:0; text-transform:none; font-weight:600; color:var(--ink-2); margin-bottom:6px;}
.fields input, .fields select{padding:11px 12px; min-height:var(--tap);}
.fields input[type=file]{padding:8px 10px;}
details.more{border-top:1px solid var(--rule); padding-top:4px;}
details.more summary{
  cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:7px;
  font-size:13.5px; font-weight:600; color:var(--ink-2); padding:8px 0; min-height:var(--tap);
}
details.more summary::-webkit-details-marker{display:none;}
details.more summary::before{
  content:""; width:7px; height:7px; border-right:1.8px solid currentColor; border-bottom:1.8px solid currentColor;
  transform:rotate(-45deg); transition:transform .15s ease; margin-left:2px;
}
details.more[open] summary::before{transform:rotate(45deg);}
details.more summary:hover{color:var(--ink);}
.fields-in{display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px; padding:4px 0 8px;}
@media (min-width:761px){ .fields-in{grid-template-columns:repeat(3, minmax(0, 1fr));} }

/* Setup steps are one tap each, the whole row. */
.step{grid-template-columns:1fr; padding:0;}
.step .go{
  display:grid; grid-template-columns:22px 1fr auto; gap:2px 12px; align-items:center; width:100%;
  background:none; border:none; text-align:left; cursor:pointer; padding:12px 0; font-family:var(--sans);
  color:var(--ink);
}
.step .go:hover .t{color:var(--accent);}
.step .go .t{grid-column:2; grid-row:1;}
.step .go .d{grid-column:2; grid-row:2;}
.step .go .mark{grid-column:1; grid-row:1 / span 2;}
.step .go .chev{
  grid-column:3; grid-row:1 / span 2; width:8px; height:8px; margin-right:6px;
  border-right:1.8px solid var(--ink-3); border-top:1.8px solid var(--ink-3); transform:rotate(45deg);
}
.step.done .go{cursor:default;}
.step.done .go .chev{display:none;}
.step.done .go:hover .t{color:var(--ink-3);}

.sec-head h2{flex:1; min-width:0;}
/* The mini reset at the top of the sheet set padding as a shorthand after the arrow
   rule had set padding-right, so every small select drew its arrow over its own text. */
select.mini{padding-right:26px;}
@media (max-width:760px){
  /* Title and button on the first line, the count underneath. Three things on one
     line at 375px squeezed the title into a column of single words. */
  .sec-head{flex-wrap:wrap; row-gap:2px; align-items:center;}
  .sec-head h2{flex:1 1 auto; white-space:nowrap;}
  .sec-head .fold{margin-left:auto;}
  .sec-head .note{flex:1 1 100%; order:3;}
  .row .side .btn{padding:8px 12px; font-size:13px; min-height:var(--tap); white-space:nowrap;}
}

/* ---------- sample data bar ---------- */
.demo-bar{position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:12px;
  padding:10px 16px;background:#121711;color:#F1F3EE;flex-wrap:wrap;}
.demo-bar p{margin:0;font-size:13.5px;line-height:1.45;flex:1 1 240px;}
.demo-dot{width:9px;height:9px;flex:0 0 auto;background:#E0A94B;border-radius:50%;}
.demo-off{min-height:44px;padding:0 16px;background:transparent;color:#F1F3EE;
  border:1px solid #5C6B57;font:inherit;font-size:14px;font-weight:600;cursor:pointer;}
.demo-off:hover{background:#1E2519;}
/* order:3 sent the sentence past the button, so a phone opened on the words "Turn it off"
   with nothing saying what it was. The dot goes at this width rather than taking a line of
   its own, and its hex is off-palette anyway. */
@media(max-width:520px){.demo-bar{padding:10px 14px;}.demo-dot{display:none;}
  .demo-bar p{flex:1 1 100%;}.demo-off{width:100%;}}

/* ---------- bulk selection on campaigns ---------- */
.bulkbar{display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:10px 0 4px;
  padding:10px 12px; background:var(--surface); border:1px solid var(--rule-2);}
.bulk-count{font-weight:700; font-size:14px;}
.bulk-acts{display:flex; align-items:center; gap:6px; flex-wrap:wrap;}
.bulk-lab{font-size:13px; color:var(--ink-2); margin-right:2px;}
.bulkbar button{min-height:44px; padding:0 13px; font:inherit; font-size:13.5px; font-weight:600;
  background:transparent; border:1px solid var(--rule-2); color:var(--ink); cursor:pointer;}
.bulkbar button:hover{background:var(--ground);}
.bulkbar .bulk-clear{margin-left:auto; border-color:transparent; color:var(--ink-2); font-weight:500;}
/* The tap target stays 44px. The negative margins let it overlap its neighbours rather
   than widen the row, which at 390px pushed the remove button 6px past the viewport. */
.row .pick{display:inline-flex; align-items:center; justify-content:center;
  min-width:44px; min-height:44px; margin:-10px -4px -10px -12px; cursor:pointer;}
.row .pick input{width:18px; height:18px; accent-color:var(--accent); cursor:pointer;}
@media(max-width:520px){.bulkbar .bulk-clear{margin-left:0;}}
