/* THE PROJECTS ROOM — its styles, moved out of app.css by the rebuild (15 Oct), in the
   order they had there. Loaded straight after app.css. */
:root:not([data-mode="light"]) .project-card:hover{ box-shadow:0 0 24px 3px var(--accent-glow); }
.pj-pill.lucid-door:hover{ text-decoration:none; color:var(--text);
    background:color-mix(in srgb, var(--text-dim) 22%, transparent); }
/* ...EXCEPT THE TASK DRAWER, WHICH IS NOT A CARD.

     The lift above assumes the element is a card painting among siblings at
     z-index:auto, where 5 is above every card and below the furniture. That
     is true of a card. It is not true of #pjDrawer, which is
     `pj-drawer glass` and so matched the selector anyway.

     The drawer is a slide-over with its OWN scrim: `.pj-drawer-scrim` is
     position:fixed at z-index:60, and `.pj-drawer` carries z-index:61 for
     exactly that reason -- the pair is a unit and 61 is measured against 60,
     not against the cards. `:has()` takes the specificity of its argument, so
     the lift scores (0,4,0) against `.pj-drawer`(0,1,0) and wins on
     specificity no matter which line comes last. The instant any select
     inside the drawer was opened, the whole drawer dropped from 61 to 5 and
     went UNDER its own scrim.

     MEASURED, NOT REASONED. With the Stage menu open in the task drawer,
     hit-testing the centre of its Not Started row returns #pjDrawerScrim,
     whose handler is onclick=pjCloseTask(). So choosing a stage did not set a
     stage: it shut the drawer. The first click -- the one that opens the menu
     -- lands fine, because the menu is not open yet and the drawer is still
     at 61. It is the click AFTER it that is swallowed, which is why this read
     as a menu that would not take a choice rather than as a layering fault.

     The drawer needs no lift at all: at 61 it is already above every sibling
     in #appZoomRoot. So it is excluded here rather than handed a bigger
     number, which would only start the same fight one storey up. The
     overflow:visible rule above is left alone -- the drawer already computes
     overflow:visible, and its inner scroller #pjDrawerBody is not .glass. */
.glass:not(.sidebar):not(.pj-drawer):has(.from-menu.open),
  .glass:not(.sidebar):not(.pj-drawer):has(.chip-input-suggestions.open),
  .glass:not(.sidebar):not(.pj-drawer):has(.snooze-menu.open),
  .glass:not(.sidebar):not(.pj-drawer):has(.acct-switcher.open),
  .glass:not(.sidebar):not(.pj-drawer):has(.tb-more-wrap.open),
  .glass:not(.sidebar):not(.pj-drawer):has(.st-drop.open){ z-index:5; }
/* PROJECTS */
/* .project-grid's own display, columns and gap were shadowed by the tile-ladder
   adoption list in app.css (same weight, later) and are gone; see that list. */
.project-card{ padding:var(--space-6); cursor:pointer; border-left:3px solid transparent;
    transition: box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
.project-card:hover{ border-color:var(--glass-border-hi); }
.project-name{ font-size:var(--type-lead); font-weight:700; margin-bottom:var(--space-1); }
.project-meta{ font-size:var(--type-body); color:var(--text-dim); margin-bottom:var(--space-3); }
.project-progress{ height:5px; border-radius:var(--radius-tight); background:rgba(120,120,140,.14); overflow:hidden; }
.project-progress-fill{ height:100%; border-radius:var(--radius-tight); }
/* Board columns are sections, so the count is dynamic — auto-fit rather than
     a hardcoded 3, and horizontally scrollable once there are more than fit. */
#projBoardWrap{ display:flex; gap:var(--space-4); align-items:flex-start; overflow-x:auto; padding-bottom:var(--space-2); }
#projBoardWrap .board-col{ flex:0 0 300px; max-width:300px; }
@media(max-width:900px){
#projBoardWrap{ display:block; }
#projBoardWrap .board-col{ max-width:none; margin-bottom:var(--space-4); }
}
.sec-name{ flex:1; min-width:0; cursor:text; }
/* See the note on .section-label: --text-faint sits on the floor. This one
     is a control, which makes it worse — 4.41:1 on parchment for something a
     person is meant to find and press. */
.sec-menu{ cursor:pointer; color:var(--text-dim); padding:0 var(--space-1); border-radius:var(--radius-control); }
.sec-menu:hover{ color:var(--text); background:rgba(120,120,140,.14); }
.drop-zone{ min-height:36px; border-radius:var(--radius-card); transition:background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.drop-zone.drag-over{ background:color-mix(in srgb, var(--accent) 12%, transparent); box-shadow:inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent); }
.proj-status-pill{ font-size:var(--type-body); font-weight:700; padding:var(--space-2) var(--space-3); border-radius:var(--radius-pill); cursor:pointer; white-space:nowrap;
    transition:background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.proj-status-pill:hover{ box-shadow:0 0 0 1px var(--glass-border-hi); }
/* The Projects home strip — see renderProjectsHome(). */
/* Custom-field chips. The colour is the option's own, carried across from
     Asana's palette — but the NAME is always printed, so the chip never relies
     on colour alone to say what it is. */
.pj-cf{ font-weight:650; }
.pj-cf-red{    color:var(--red,#E06C6C);    background:color-mix(in srgb, var(--red,#E06C6C) 16%, transparent); }
.pj-cf-amber{  color:var(--amber,#C08A2E);  background:color-mix(in srgb, var(--amber,#C08A2E) 18%, transparent); }
.pj-cf-green{  color:var(--green,#3E9E70);  background:color-mix(in srgb, var(--green,#3E9E70) 16%, transparent); }
.pj-cf-blue{   color:var(--blue,#5B8DEF);   background:color-mix(in srgb, var(--blue,#5B8DEF) 16%, transparent); }
.pj-cf-teal{   color:var(--teal,#3E9E9E);   background:color-mix(in srgb, var(--teal,#3E9E9E) 16%, transparent); }
.pj-cf-purple{ color:var(--purple,#8E7BD8); background:color-mix(in srgb, var(--purple,#8E7BD8) 16%, transparent); }
.pj-cf-pink{   color:var(--pink,#D473A8);   background:color-mix(in srgb, var(--pink,#D473A8) 16%, transparent); }
.pj-cf-grey{   color:var(--text-dim);       background:rgba(120,120,140,.16); }
/* .pj-home-queue and .pj-home-clear drew the two full-width slabs under the
     project tiles and the sentence that replaced them on a clear week. Row 1734
     removed both — see renderProjectsHome. The rules are gone rather than left
     behind matching nothing. */
/* ==========================================================
     ROW 1737 — THE PROJECT KPI TILES, IN ONE ROW.

     "The project KPI tiles are too big; all five sit in one
      row."   (Matt, 11 September.)

     SCOPED TO #pjHomeStrip ON PURPOSE. .pulse-tiles is the
     shared tile grid — Pulse and several other pages draw
     through it, each with its own number of tiles, and a
     column count set globally to suit five would break every
     page that has four or six. So the width rule lives with
     the page that has the requirement.

     Five is stated as a count rather than left to auto-fit,
     because auto-fit answers "how many fit at this minimum",
     and the minimum that fits five on his screen puts four on
     a slightly narrower one — which is exactly the 4 + 1 wrap
     he is looking at. Below 1180px it steps to three and below
     820px to two, so the row breaks evenly instead of leaving
     one tile stranded on a line of its own.

     AND SMALLER, which is the other half of the row. The 29px
     figure and 15px of padding were sized for a page of six
     tiles that IS the page; here the tiles are a strip above
     the projects and they were taller than the project cards
     underneath them. minmax(0,1fr) rather than auto so a long
     foot line cannot push a column wider than its share.
     ========================================================== */
#pjHomeStrip .pulse-tiles{ grid-template-columns:repeat(5, minmax(0,1fr)); gap:var(--space-3);
    margin-bottom:var(--space-5); }
#pjHomeStrip .pulse-tile{ padding:var(--space-3) var(--space-3) var(--space-3); gap:var(--space-1); border-radius:var(--radius-card); }
#pjHomeStrip .pt-label{ font-size:var(--type-label); letter-spacing:.055em; }
#pjHomeStrip .pt-n{ font-size:var(--type-title); }
#pjHomeStrip .pt-foot{ font-size:var(--type-label); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@media(max-width:1180px){
#pjHomeStrip .pulse-tiles{ grid-template-columns:repeat(3, minmax(0,1fr)); }
}
@media(max-width:820px){
#pjHomeStrip .pulse-tiles{ grid-template-columns:repeat(2, minmax(0,1fr)); }
}
/* ==========================================================
     ROW 1736 — FILES ON A PROJECT.

     The panel IS the drop target, and it says so before
     anything is dragged over it — "drop them anywhere on this
     panel" in the subtitle — rather than only revealing itself
     once something is already in the air. A target you have to
     guess at is a target most people never find.

     THE EXTENSION IS THE ICON. Three or four letters in a
     tinted square, in the mono face the rest of this app uses
     for anything machine-shaped. An icon set would need a
     picture per format and would still fall back to a generic
     page for the one he actually has, and a wall of identical
     generic pages tells you nothing.

     A ROW IS A BUTTON AND SO IS ITS ×, and they do different
     things, so the × is a real button rather than a span with
     a click on it — it has to be reachable by keyboard and it
     has to say what it removes, because it removes the file.
     ========================================================== */
.pj-files{ position:relative; padding:var(--space-5) var(--space-5) var(--space-5); border-radius:var(--radius-card);
    border:1px dashed color-mix(in srgb, var(--glass-border-hi) 70%, transparent); }
.pj-files-head{ display:flex; align-items:flex-start; gap:var(--space-4); margin-bottom:var(--space-4); }
.pj-files-head > div:first-child{ flex:1; min-width:0; }
.pj-files-list{ display:grid; gap:var(--space-2);
    grid-template-columns:repeat(auto-fill, minmax(min(100%, 320px), 1fr)); }
.pj-file{ display:flex; align-items:center; gap:var(--space-3); padding:var(--space-2) var(--space-3); border-radius:var(--radius-card);
    border:1px solid var(--glass-border); background:var(--wash-soft); min-width:0;
    transition:border-color var(--dur-fast) var(--ease-out),
               box-shadow var(--dur-fast) var(--ease-out),
               transform var(--dur-fast) var(--ease-out); }
.pj-file:hover{ border-color:var(--glass-border-hi); box-shadow:var(--elev-raised); }
.pj-file-kind{ flex:none; width:42px; height:42px; border-radius:var(--radius-card);
    display:flex; align-items:center; justify-content:center;
    font-family:var(--font-mono); font-size:var(--type-label); font-weight:600;
    letter-spacing:.02em; text-transform:uppercase; overflow:hidden;
    color:var(--accent);
    background:color-mix(in srgb, var(--accent) 12%, transparent);
    border:1px solid color-mix(in srgb, var(--accent) 26%, transparent); }
.pj-file-main{ flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--space-hair);
    text-align:left; background:none; border:0; padding:0; font-family:inherit; cursor:pointer; }
.pj-file-name{ font-size:var(--type-body); font-weight:var(--weight-strong); color:var(--text);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.pj-file-main:hover .pj-file-name{ text-decoration:underline; }
.pj-file-sub{ font-size:var(--type-label); color:var(--text-faint); }
.pj-file-x{ flex:none; width:28px; height:28px; border-radius:var(--radius-control); border:0; cursor:pointer;
    background:none; color:var(--text-faint); font-size:var(--type-title); line-height:1; font-family:inherit; }
.pj-file-x:hover{ color:var(--pink); background:color-mix(in srgb, var(--pink) 12%, transparent); }
.pj-file-x:focus-visible{ outline:2px solid var(--ring); outline-offset:2px; }
.pj-file.is-pending{ opacity:.62; }
.pj-file.is-pending .pj-file-main{ cursor:default; }
.pj-file.is-failed{ opacity:1;
    border-color:color-mix(in srgb, var(--pink) 45%, transparent);
    background:color-mix(in srgb, var(--pink) 8%, transparent); }
.pj-file.is-failed .pj-file-sub{ color:var(--pink); font-weight:600; }
.pj-file.is-failed .pj-file-kind{ color:var(--pink);
    background:color-mix(in srgb, var(--pink) 12%, transparent);
    border-color:color-mix(in srgb, var(--pink) 30%, transparent); }
:root[data-mode="light"] .pj-file.is-failed .pj-file-sub{
    color:color-mix(in srgb, var(--pink) 72%, #000); }
.pj-files-empty{ font-size:var(--type-body); color:var(--text-faint); padding:var(--space-5) var(--space-1); }
.pj-files-empty.is-wrong{ color:var(--orange); font-weight:600; line-height:1.5;
    padding:var(--space-4) var(--space-4); border-radius:var(--radius-card);
    border:1px solid color-mix(in srgb, var(--orange) 38%, transparent);
    background:color-mix(in srgb, var(--orange) 9%, transparent); }
:root[data-mode="light"] .pj-files-empty.is-wrong{
    color:color-mix(in srgb, var(--orange) 72%, #000); }
.pj-files-drop-hint{ position:absolute; inset:0; display:none; align-items:center;
    justify-content:center; border-radius:var(--radius-card); font-size:var(--type-lead); font-weight:700;
    letter-spacing:.01em; color:var(--accent); pointer-events:none;
    background:color-mix(in srgb, var(--accent) 12%, var(--glass-bg)); }
.pj-files.is-over{ border-color:var(--accent);
    border-style:solid; }
.pj-files.is-over .pj-files-drop-hint{ display:flex; }
/* ==========================================================
     ROW 1736, SECOND PASS — "it feels like something that was
     assembled with cheap parts."   (Matt, 11 September.)

     The first version worked and looked like a settings page.
     Three things were wrong with it and all three are the same
     fault: nothing on the panel was using the width, the
     colour or the content it had.

     ONE — A COLUMN OF FULL-WIDTH ROWS. On his display the
     panel is about 1,770px across and each row held a filename
     at the left, a size under it, and a × seventeen hundred
     pixels away. Nine files were nine stripes with a field of
     nothing down the middle. It is a GRID now — auto-fill
     against a 320px minimum, so three or four cards on a wide
     screen and one on a phone, and the eye travels a card
     rather than a stripe. min(100%, 320px) rather than a bare
     320px because a single card must not be wider than the
     panel on a narrow window.

     TWO — EVERY PLATE WAS THE SAME COLOUR. Accent green on
     accent green, a contract indistinguishable from a
     spreadsheet until its three letters had been read. The
     FAMILY now picks the hue, from the --cat-* set the app
     already uses for contact categories: red for the signed
     thing, green for the numbers, blue for the document,
     amber for the deck, violet for a picture, slate for
     everything else. One custom property per card, so the
     plate, its ring and its tint all move together, and every
     value is a theme token — there is not a hex in here and
     there must never be, because there are thirteen themes.

     THREE — AND THE PICTURES ARE PICTURES. See pjThumbLoad:
     an image file draws itself in its plate, fetched only when
     the plate is actually on screen and only for images. A
     wall of squares reading "JPG" over a project of site
     photographs was the cheapest thing this panel did.

     THE × STAYS VISIBLE AND DIM rather than appearing on
     hover, for the reason written over .agenda-act: hover does
     not exist on the phone, and a control that only a mouse
     can find is a control half his devices do not have.
     ========================================================== */
.pj-files-list > .pj-files-empty{ grid-column:1 / -1; }
.pj-files-list > .pj-file.is-failed{ grid-column:1 / -1; }
.pj-file[data-fam="pdf"]{   --pj-fam:var(--cat-red);    --pj-ink:var(--cat-red-text); }
.pj-file[data-fam="sheet"]{ --pj-fam:var(--cat-green);  --pj-ink:var(--cat-green-text); }
.pj-file[data-fam="doc"]{   --pj-fam:var(--cat-blue);   --pj-ink:var(--cat-blue-text); }
.pj-file[data-fam="slide"]{ --pj-fam:var(--cat-orange); --pj-ink:var(--cat-orange-text); }
.pj-file[data-fam="image"]{ --pj-fam:var(--cat-violet); --pj-ink:var(--cat-violet-text); }
.pj-file[data-fam="text"]{  --pj-fam:var(--cat-slate);  --pj-ink:var(--cat-slate-text); }
.pj-file[data-fam="file"]{  --pj-fam:var(--cat-slate);  --pj-ink:var(--cat-slate-text); }
/* Two classes deep so it wins over the plain .pj-file-kind above wherever it
     happens to sit in the file, and still loses to .pj-file.is-failed
     .pj-file-kind, which is three deep and is a different statement — a file
     that did not upload is not a kind of file. */
.pj-file .pj-file-kind{ position:relative; width:52px; height:52px;
    border-radius:var(--radius-control); font-size:var(--type-label);
    color:var(--pj-ink, var(--accent));
    background:color-mix(in srgb, var(--pj-fam, var(--accent)) 13%, transparent);
    border:1px solid color-mix(in srgb, var(--pj-fam, var(--accent)) 28%, transparent); }
:root[data-mode="light"] .pj-file .pj-file-kind{
    background:color-mix(in srgb, var(--pj-fam, var(--accent)) 10%, transparent);
    border-color:color-mix(in srgb, var(--pj-fam, var(--accent)) 38%, transparent);
    /* AND THE LETTERS GO DARKER, towards the theme's OWN ink rather than
       towards black. --cat-green and --cat-slate are mid-tones; on paper they
       measured 3.54 and 3.58:1, under the 4.5 the readability harness holds
       everything to. Mixed 72/28 with --text they land between 4.6 and 6.3 on
       every light theme and keep their hue, which is the entire point of
       colouring them. Mixing towards --text rather than towards a literal
       black is what makes this one rule right on all thirteen themes. */
    color:color-mix(in srgb, var(--pj-ink, var(--accent)) 72%, var(--text)); }
.pj-file-ext{ display:block; }
.pj-file-kind.has-thumb img{ position:absolute; inset:0; width:100%; height:100%;
    object-fit:cover; border-radius:inherit; display:block; }
.pj-file .pj-file-x{ opacity:.5;
    transition:opacity var(--dur-fast) var(--ease-out); }
.pj-file:hover .pj-file-x{ opacity:1; }
@media (prefers-reduced-motion: reduce){
.pj-file{ transition:none; }
.pj-file .pj-file-x{ transition:none; }
}
.proj-empty-sec{ color:var(--text-faint); font-size:var(--type-body); padding:var(--space-3) var(--space-1); }
.list-sec-head{ display:flex; align-items:center; gap:var(--space-2); margin:var(--space-5) 0 var(--space-2); }
.list-sec-head .section-label{ margin:0; }
.board-col{ padding:var(--space-4); min-height:100px; }
.board-col-head{ display:flex; align-items:center; gap:var(--space-2); justify-content:space-between; font-size:var(--type-body); font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--text-faint); margin-bottom:var(--space-3); }
.board-col-count{ background:rgba(120,120,140,.14); border-radius:var(--radius-pill); padding:1px var(--space-2); font-size:var(--type-label); color:var(--text); }
.task-card-title{ font-size:var(--type-body); font-weight:600; margin-bottom:var(--space-2); }
.task-chip{ width:20px; height:20px; border-radius:var(--radius-round); background:rgba(120,120,140,.18); display:inline-flex; align-items:center; justify-content:center; font-size:var(--type-label); font-weight:700; flex:none; }
.list-title{ flex:1; font-size:var(--type-body); font-weight:500; }
.pc-head{ display:flex; align-items:center; gap:var(--space-2); margin-bottom:var(--space-3); }
.pc-weekdays{ display:grid; grid-template-columns:repeat(7,1fr); font-size:var(--type-label); color:var(--text-faint); text-align:center; margin-bottom:var(--space-1); }
.pc-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:var(--space-2); }
.pc-cell{ min-height:74px; border-radius:var(--radius-card); background:rgba(120,120,140,.05); border:1px solid var(--glass-border); padding:var(--space-2); }
.pc-cell.pc-pad{ background:none; border:none; }
.pc-daynum{ font-size:var(--type-label); color:var(--text-faint); margin-bottom:var(--space-1); }
.pc-chip{ font-size:var(--type-label); background:rgba(120,120,140,.14); border-radius:var(--radius-control); padding:var(--space-hair) var(--space-2); margin-bottom:var(--space-1); cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pc-chip:hover{ background:rgba(120,120,140,.22); }
/* ==========================================================
     PROJECTS, SECOND PASS — the parts a real project tool has.
     ----------------------------------------------------------
     What was here was a board, a list and a month grid, and every one of them
     showed a title and a date. That is a task list with columns. The gap
     between it and the thing he runs his week in is not one feature, it is
     about twenty small ones: the chips that tell you a card's state without
     opening it, a drawer with every field on it rather than a modal with four,
     a timeline you can see a slip coming on, and numbers at the end of it.
     All of it in Lucid's own tokens — no borrowed palette, no second design.
     ========================================================== */
.pj-chip{ display:inline-flex; align-items:center; gap:var(--space-1); font-size:var(--type-label); font-weight:700;
    padding:var(--space-hair) var(--space-2); border-radius:var(--radius-pill); white-space:nowrap; line-height:1.6; }
.pj-chip-hi{ background:color-mix(in srgb, var(--cat-red) 18%, transparent); color:var(--cat-red-text); }
.pj-chip-med{ background:color-mix(in srgb, var(--orange) 18%, transparent); color:var(--orange-text); }
.pj-chip-low{ background:color-mix(in srgb, var(--blue) 16%, transparent); color:var(--blue-text); }
.pj-chip-none{ background:transparent; color:var(--text-faint); border:1px dashed var(--glass-border-hi); }
.pj-chip-overdue{ background:color-mix(in srgb, var(--cat-red) 20%, transparent); color:var(--cat-red-text); }
.pj-chip-soon{ background:color-mix(in srgb, var(--orange) 18%, transparent); color:var(--orange-text); }
.pj-chip-date{ background:rgba(120,120,140,.14); color:var(--text-dim); font-weight:600; }
.pj-chip-blocked{ background:color-mix(in srgb, var(--violet) 20%, transparent); color:var(--violet-text); }
.pj-chip-linked{ background:color-mix(in srgb, var(--teal) 18%, transparent); color:var(--teal-text); }
.pj-chip-sub{ background:rgba(120,120,140,.14); color:var(--text-dim); font-weight:600; }
:root[data-mode="light"] .pj-chip-hi,
  :root[data-mode="light"] .pj-chip-overdue{ color:color-mix(in srgb, var(--cat-red) 72%, var(--neutral-2)); }
:root[data-mode="light"] .pj-chip-med,
  :root[data-mode="light"] .pj-chip-soon{ color:color-mix(in srgb, var(--orange) 72%, var(--neutral-2)); }
:root[data-mode="light"] .pj-chip-low{ color:color-mix(in srgb, var(--blue) 72%, var(--neutral-2)); }
:root[data-mode="light"] .pj-chip-blocked{ color:color-mix(in srgb, var(--violet) 72%, var(--neutral-2)); }
:root[data-mode="light"] .pj-chip-linked{ color:color-mix(in srgb, var(--teal) 72%, var(--neutral-2)); }
:root[data-mode="light"] .pj-chip-sub,
  :root[data-mode="light"] .pj-chip-date{ color:var(--text); }
:root:not([data-mode="light"]) .pj-chip-hi,
  :root:not([data-mode="light"]) .pj-chip-overdue{ color:color-mix(in srgb, var(--cat-red-2) 62%, #fff); }
:root:not([data-mode="light"]) .pj-chip-med,
  :root:not([data-mode="light"]) .pj-chip-soon{ color:color-mix(in srgb, var(--orange-2) 70%, #fff); }
:root[data-mode="light"] .proj-status-pill{ font-weight:800; }
:root[data-mode="light"] .pj-fav{ color:var(--text-dim); }
.pj-chip[onclick]{ cursor:pointer; }
.pj-chip[onclick]:hover{ filter:brightness(1.12); }
.pj-head{ display:flex; align-items:flex-start; gap:var(--space-4); margin-bottom:var(--space-2); flex-wrap:wrap; }
.pj-dot{ width:12px; height:12px; border-radius:var(--radius-tight); flex:none; margin-top:var(--space-2); cursor:pointer; }
.pj-head-main{ flex:1; min-width:220px; }
.pj-progress{ height:5px; border-radius:var(--radius-tight); background:rgba(120,120,140,.16); overflow:hidden; max-width:320px; margin-top:var(--space-2); }
.pj-progress > span{ display:block; height:100%; border-radius:var(--radius-tight); transition:width var(--dur-base) var(--ease-out); }
.pj-toolbar{ display:flex; align-items:center; gap:var(--space-3); flex-wrap:wrap; margin:var(--space-4) 0 var(--space-4); }
.pj-search{ position:relative; flex:0 1 240px; }
.pj-search input{ width:100%; background:rgba(120,120,140,.08); border:1px solid var(--glass-border);
    border-radius:var(--radius-card); color:var(--text); font-family:var(--font-body); font-size:var(--type-body); padding:var(--space-2) var(--space-3); }
.pj-search input:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
.pj-filter{ display:flex; gap:var(--space-1); padding:var(--space-1); border-radius:var(--radius-card); background:rgba(120,120,140,.08); }
.pj-filter div{ padding:var(--space-2) var(--space-3); border-radius:var(--radius-control); font-size:var(--type-body); font-weight:600; color:var(--text-dim); cursor:pointer; }
.pj-filter div.active{ background:color-mix(in srgb, var(--blue) 20%, transparent); color:var(--text); }
.pj-card-row{ display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap; margin-top:var(--space-2); }
.pj-card-top{ display:flex; align-items:flex-start; gap:var(--space-2); }
.pj-card-check{ width:15px; height:15px; border-radius:var(--radius-round); border:2px solid var(--glass-border-hi);
    flex:none; margin-top:var(--space-hair); cursor:pointer; transition:all var(--dur-fast) var(--ease-out); }
.pj-card-check:hover{ border-color:var(--green); }
.pj-card-check.done{ background:var(--green); border-color:var(--green); }
.pj-add-inline{ width:100%; background:transparent; border:1px dashed var(--glass-border-hi); border-radius:var(--radius-card);
    color:var(--text-faint); font-family:var(--font-body); font-size:var(--type-body); padding:var(--space-2) var(--space-3); cursor:pointer; text-align:left; }
.pj-add-inline:hover{ color:var(--text); border-color:var(--accent); }
.pj-add-inline:focus{ outline:none; color:var(--text); border-style:solid; border-color:color-mix(in srgb, var(--accent) 50%, transparent); cursor:text; }
/* ---- list: cards in a grid, and the late ones glowing ------------------
     Matt: "task tasks on project need to glow red for being past due. They
     also need to be little cards instead of a row that goes all the way
     accross the page."
     The grid is the one the project cards already use — auto-fill on a floor
     wide enough for a title and a row of chips — so List reads as the same
     family of surfaces as the rest of the app rather than a table nobody
     styled. The card itself is .task-card, unchanged, borrowed from Board. */
/* .pj-task-grid and .pj-rep-grid take display, columns, gap and alignment from the
   tile-ladder adoption list in app.css, which always beat the declarations that
   stood here; only what that list does not set remains. */
.pj-task-grid .task-card{ margin-bottom:0; }
/* The empty-section line is a sentence, not a card — it takes the row. */
.pj-task-grid .proj-empty-sec{ grid-column:1 / -1; }
.pj-add-list{ max-width:250px; }
.pj-card-kids{ margin-top:var(--space-2); padding-top:var(--space-2); display:grid; gap:var(--space-2);
    border-top:1px solid var(--glass-border); }
.pj-card-kid{ display:flex; align-items:center; gap:var(--space-2); cursor:pointer; }
@keyframes pjLateBreath{
    0%, 100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--cat-red-text) 0%, transparent); }
    50%     { box-shadow:0 0 16px -2px color-mix(in srgb, var(--cat-red-text) 60%, transparent); }
  }
/* ---- timeline / gantt ---- */
.pj-tl-wrap{ overflow-x:auto; padding-bottom:var(--space-3); }
.pj-tl{ min-width:100%; position:relative; }
.pj-tl-head{ display:flex; position:sticky; top:0; z-index:2; }
.pj-tl-labels{ width:230px; flex:none; }
.pj-tl-days{ display:flex; }
.pj-tl-day{ width:32px; flex:none; text-align:center; font-size:var(--type-label); font-weight:700;
    color:var(--text-faint); padding:var(--space-1) 0; border-left:1px solid var(--glass-border); }
.pj-tl-day.wknd{ background:rgba(120,120,140,.07); }
.pj-tl-day.today{ background:color-mix(in srgb, var(--accent) 18%, transparent); color:var(--text); border-radius:var(--radius-control) var(--radius-control) 0 0; }
.pj-tl-months{ display:flex; }
.pj-tl-mon{ font-size:var(--type-label); font-weight:700; color:var(--text-dim); text-transform:uppercase;
    letter-spacing:.06em; padding:var(--space-hair) 0 var(--space-1) var(--space-2); flex:none; border-left:1px solid var(--glass-border-hi);
    overflow:hidden; white-space:nowrap; }
.pj-tl-row{ display:flex; align-items:center; height:40px; }
.pj-tl-name{ width:230px; flex:none; font-size:var(--type-body); padding-right:var(--space-3); cursor:pointer;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pj-tl-name:hover{ text-decoration:underline; }
.pj-tl-track{ position:relative; height:40px; flex:none; }
.pj-tl-grid{ position:absolute; inset:0; display:flex; pointer-events:none; }
.pj-tl-grid span{ width:32px; flex:none; border-left:1px solid var(--glass-border); }
.pj-tl-grid span.wknd{ background:rgba(120,120,140,.07); }
.pj-tl-bar{ position:absolute; top:11px; height:18px; border-radius:var(--radius-control); cursor:pointer; }
.pj-tl-bar:focus{ outline:2px solid var(--ring); }
.pj-tl-bar:hover{ filter:brightness(1.1); }
.pj-tl-bar.done{ opacity:.5; }
.pj-tl-nodate{ font-size:var(--type-label); color:var(--text-faint); padding-left:var(--space-2); }
.pj-tl-today{ position:absolute; top:0; bottom:0; width:2px; background:var(--cat-red); opacity:.7; pointer-events:none; }
.pj-rep-grid{ margin-bottom:var(--space-5); }
.pj-stat{ padding:var(--space-4) var(--space-5); border-radius:var(--radius-card); background:var(--glass-bg); border:1px solid var(--glass-border); }
.pj-stat-num{ font-size:var(--type-figure); font-weight:700; letter-spacing:-.02em; line-height:1.1; }
.pj-stat-lbl{ font-size:var(--type-label); font-weight:700; text-transform:uppercase; letter-spacing:.07em; color:var(--text-faint); margin-top:var(--space-2); }
.pj-bars{ display:flex; flex-direction:column; gap:var(--space-2); }
.pj-bar-row{ display:flex; align-items:center; gap:var(--space-3); font-size:var(--type-body); }
.pj-bar-name{ width:150px; flex:none; color:var(--text-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pj-bar-track{ flex:1; height:9px; border-radius:var(--radius-control); background:rgba(120,120,140,.14); overflow:hidden; }
.pj-bar-track > span{ display:block; height:100%; border-radius:var(--radius-control); }
.pj-bar-val{ width:64px; flex:none; text-align:right; color:var(--text-faint); font-size:var(--type-body);
    font-family:var(--font-mono); }
/* ---- the drawer ----
     A slide-over, not a centred modal. A task has twenty fields on it and the
     board behind it is the context for all of them — covering the board to edit
     a card on it is exactly backwards. */
.pj-drawer-scrim{ position:fixed; inset:0; z-index:60; background:rgba(4,6,16,0.28);
    opacity:0; pointer-events:none; transition:opacity var(--dur-base) var(--ease-out); }
:root[data-mode="light"] .pj-drawer-scrim{ background:rgba(62,50,38,0.18); }
.pj-drawer-scrim.open{ opacity:1; pointer-events:auto; }
/* AN OPAQUE PANEL, not a pane of glass.
     .glass paints `var(--glass-sheen), var(--glass-bg)` and both are
     translucent, so what is BEHIND a glass panel changes the contrast of the
     text on it. That is fine for a card sitting on the page background and
     wrong for a drawer, which slides over whatever happened to be underneath —
     and once a project's own colour started tinting the view behind it, the
     Delete label measured 4.28:1 against a 4.5 floor. It was legible over the
     old grey and would have gone on being legible right up until the day a
     project was given a pale colour.
     A solid ground under the glass fixes the whole class: the drawer now reads
     the same over any page, in any theme, whatever colour the project is. */
/* 100% AND NOT 100vw, AND THIS IS THE WHOLE OF "PROJECTS TOO WIDE - TEXT ON
     SIDE TRAY" (Matt, 25 September, on the iPhone).

     MEASURED, real WebKit at 393x852, the size of his phone:

         at 100%   the tray is left 0, right 393, width 393   correct
         at 115%   the tray is left -59, right 393, width 452  59px off the left

     He runs the app at 115%, because at 100% the buttons are too small to press
     (his third fault). So this is what he is actually looking at: a 452-point
     tray on a 393-point screen, anchored to the right edge, with the first 59
     points of every line of text off the side of the screen. Inside it the
     buttons run the other way - .btn-danger measured right 438, which is 45
     points past the right edge.

     WHY vw IS WRONG HERE SPECIFICALLY. applyFontScale() puts
     `transform: scale(z)` on #appZoomRoot, which makes that element the
     containing block for every position:fixed descendant. So this tray's box is
     resolved against #appZoomRoot - which is deliberately counter-sized to
     100/z vw - and then the whole thing is scaled back up by z. A `vw` length
     does not take part in that: it measures the real viewport, so it comes out
     z times too big. 393 x 1.15 = 452, which is the number above.

     `100%` is the same length as `100vw` at every scale on the desktop, where
     #appZoomRoot is one viewport wide, and it is the RIGHT length at every
     scale on the phone, because it follows the counter-scaled parent instead of
     ignoring it. The sibling .pj-drawer-scrim was already immune, because it
     sizes itself with `inset:0` rather than vw - which is why the dimmed
     backdrop lined up while the tray did not.

     THE SAME FAULT IS STILL IN THREE MORE PLACES, all position:fixed and all
     sized in vw/vh: .chat-card (14545), the 300px popover at 17661, and
     .pj-drawer's own narrow-screen rule below. The one below is fixed with it;
     the other two are named in claude/IP-25-SEPT-*.md and are not this fault. */
.pj-drawer{ position:fixed; top:0; right:0; bottom:0; width:min(560px, 100%); z-index:61;
    display:flex; flex-direction:column; border-radius:0; border-right:none; border-top:none; border-bottom:none;
    background-color: var(--bg-mid);
    transform:translateX(102%); transition:transform var(--dur-slow) var(--ease-out); }
.pj-drawer.open{ transform:none; }
@media(max-width:600px){
.pj-drawer{ width:100%; }
}
.pj-drawer-head{ padding:var(--space-5) var(--space-6) var(--space-3); border-bottom:1px solid var(--glass-border); flex:none; }
.pj-unit{ font-size:var(--type-label); color:var(--text-faint); }
.pj-drawer-title{ font-family:var(--font-display); font-size:var(--type-title); font-weight:600; line-height:1.25;
    outline:none; border-radius:var(--radius-control); padding:var(--space-hair) var(--space-1); margin:calc(var(--space-hair) * -1) calc(var(--space-1) * -1); }
.pj-drawer-title:focus{ background:rgba(120,120,140,.10); box-shadow:0 0 0 1px var(--glass-border-hi); }
.pj-drawer-body{ flex:1; min-height:0; overflow-y:auto; padding:var(--space-4) var(--space-6) var(--space-10); scrollbar-width:thin; }
.pj-field{ display:flex; align-items:center; gap:var(--space-3); padding:var(--space-2) 0; border-bottom:1px solid var(--glass-border); }
.pj-field > label{ width:112px; flex:none; font-size:var(--type-label); font-weight:700; text-transform:uppercase;
    letter-spacing:.06em; color:var(--text-faint); }
.pj-field > .pj-field-val{ flex:1; min-width:0; display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap; }
.pj-input{ width:100%; background:rgba(120,120,140,.08); border:1px solid var(--glass-border); border-radius:var(--radius-control);
    color:var(--text); font-family:var(--font-body); font-size:var(--type-body); padding:var(--space-2) var(--space-3); }
.pj-input:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
.pj-seg{ display:flex; gap:var(--space-1); }
.pj-seg div{ padding:var(--space-2) var(--space-3); border-radius:var(--radius-control); font-size:var(--type-body); font-weight:700; cursor:pointer;
    color:var(--text-dim); border:1px solid var(--glass-border-hi); }
.pj-seg div.on{ color:var(--text); border-color:transparent; }
.pj-seg div.on[data-p="high"]{ background:color-mix(in srgb, var(--cat-red) 22%, transparent); }
.pj-seg div.on[data-p="medium"]{ background:color-mix(in srgb, var(--orange) 22%, transparent); }
.pj-seg div.on[data-p="low"]{ background:color-mix(in srgb, var(--blue) 22%, transparent); }
.pj-seg div.on[data-p="none"]{ background:rgba(120,120,140,.18); }
.pj-toggle{ width:38px; height:21px; border-radius:var(--radius-pill); background:rgba(120,120,140,.26); position:relative;
    cursor:pointer; flex:none; transition:background var(--dur-fast) var(--ease-out); }
.pj-toggle::after{ content:''; position:absolute; top:2.5px; left:2.5px; width:16px; height:16px; border-radius:var(--radius-round);
    background:var(--text); transition:transform var(--dur-fast) var(--ease-out); }
.pj-toggle.on{ background:var(--green); }
.pj-toggle.on::after{ transform:translateX(17px); background:#fff; }
.pj-done-row{ display:flex; align-items:center; gap:var(--space-3); font-size:var(--type-body); font-weight:600; margin-bottom:var(--space-1); }
.pj-photo{ display:block; flex:1 1 100%; min-width:0; }
.pj-photo img{ display:block; max-width:100%; max-height:260px; border-radius:var(--radius-control); border:1px solid var(--glass-border); }
.pj-photo-said{ color:var(--text-dim); font-size:var(--type-body); }
.pj-sub-row{ display:flex; align-items:center; gap:var(--space-2); padding:var(--space-2) 0; font-size:var(--type-body); }
.pj-sub-row .list-check{ cursor:pointer; }
.pj-sub-row .x{ margin-left:auto; cursor:pointer; color:var(--text-faint); padding:0 var(--space-1); }
.pj-sub-row .x:hover{ color:var(--cat-red-text); }
.pj-sub-open{ margin-left:auto; font-size:var(--type-label); color:var(--text-faint); cursor:pointer; }
.pj-sub-open:hover{ color:var(--text); text-decoration:underline; }
.pj-pill-list{ display:flex; flex-wrap:wrap; gap:var(--space-2); }
.pj-pill{ display:inline-flex; align-items:center; gap:var(--space-2); font-size:var(--type-body); font-weight:600;
    padding:var(--space-1) var(--space-2); border-radius:var(--radius-pill); background:rgba(120,120,140,.14); }
.pj-pill .x{ cursor:pointer; color:var(--text-faint); font-size:var(--type-body); line-height:1; }
.pj-pill .x:hover{ color:var(--cat-red-text); }
.pj-timer{ display:flex; align-items:center; gap:var(--space-3); flex-wrap:wrap; }
.pj-timer-run{ font-family:var(--font-mono); font-size:var(--type-body); color:var(--green-text); font-weight:700; }
.pj-notes{ width:100%; min-height:96px; resize:vertical; background:rgba(120,120,140,.08);
    border:1px solid var(--glass-border); border-radius:var(--radius-card); color:var(--text);
    font-family:var(--font-body); font-size:var(--type-body); line-height:1.6; padding:var(--space-3) var(--space-3); }
.pj-notes:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
.pj-readstate{ position:relative; padding:var(--space-4) var(--space-4) var(--space-4) var(--space-4);
    border-radius:var(--radius-card); background:color-mix(in srgb, var(--text) 4%, transparent);
    display:flex; flex-direction:column; align-items:flex-start; gap:var(--space-3); }
.pj-readstate::before{ content:''; position:absolute; left:0; top:var(--space-3); bottom:var(--space-3);
    width:2px; border-radius:var(--radius-tight); background:color-mix(in srgb, var(--text) 13%, transparent); }
.pj-readstate.failed::before{ background:var(--accent); }
.pj-readstate-line{ font-size:var(--type-body); line-height:1.55; color:var(--text); }
.pj-readstate-why{ font-size:var(--type-label); line-height:1.5; color:var(--text-dim); margin-top:calc(var(--space-2) * -1); }
/* ROW 2231. The same block, under the five tiles rather than in place of the
     project cards — it is the same news about a different read and it should
     not look like a different kind of news. It needs the air the cards version
     gets from the grid it sits in. */
#pjHomeStrip .pj-readstate{ margin-top:var(--space-4); }
/* ---- project cards & rail groups ---- */
.pj-group-head{ display:flex; align-items:center; gap:var(--space-2); margin:var(--space-6) 0 var(--space-3); }
.pj-group-head:first-child{ margin-top:0; }
.pj-fav{ cursor:pointer; color:var(--text-faint); font-size:var(--type-body);
    transition:color var(--dur-fast) var(--ease-out); }
.pj-fav:hover{ color:var(--orange-text); }
.pj-fav.on{ color:var(--orange-text); }
.overdue-mark{ color:var(--cat-red-2); }
:root[data-mode="light"] .overdue-mark{ color:var(--cat-red); }
.ed-switch input:checked + .pj-toggle{ background:var(--green); }
.ed-switch input:checked + .pj-toggle::after{ transform:translateX(17px); background:#fff; }
.ed-switch input:focus-visible + .pj-toggle{ box-shadow:0 0 0 2px var(--glass-border-hi); }
/* THREE ACROSS ON THE PROJECTS PAGE — 9 October 2026.
   ===========================================================================
   Matt, 9 October: "project cards should be 3 across on main page."

   WHAT WAS ACTUALLY ON SCREEN, MEASURED BEFORE ANYTHING WAS CHANGED. Exactly
   two rules in this file set .project-grid's columns, and no more: the one at
   the top of the PROJECTS block, which asks auto-fill for 230px minimums, and
   the adoption list above, which asks it for --tile-min. Both are plain
   .project-grid, so they have the same weight, the later one wins, and the
   230px rule has been dead since the 11 September adoption pass. On his
   1481x690 window the winner already gave THREE tracks of 420px. On the 1920
   monitor it gave FOUR of 420px, because --tile-span steps to --tile-span-4
   at 1704 and auto-fill fills whatever room it is given. So the page he was
   looking at when he wrote that line was the four-across one: three was
   already right on the window, and the two screens disagreed, which is the
   one thing the ladder above exists to prevent.

   SO THE COUNT IS TOLD RATHER THAN ASKED FOR, like the contact lists four
   rules up. Three across is --tile-span-3 — --tile-ideal * 3 + --tile-gap * 2,
   1296px — and the cap matters as much as the count: the note at the 1099
   tier records what a forced count does when it meets a cap built for a
   different one. With the cap at --tile-span-3 each card comes out at exactly
   --tile-ideal, 420px, on the window and on the monitor alike, inside the
   340-460px band tests/test-no-tile-stretches.js holds these cards to.

   THE COST ON THE MONITOR, STATED RATHER THAN DISCOVERED. At 1920 the page
   column is --tile-span-4, 1734px, while a row of three is 1296px, so the
   cards end 438px short of the page's right edge. Three cards cannot fill a
   four-card page without going over --tile-max; that is arithmetic, not an
   oversight. It is the price of the two screens agreeing, layout is his call,
   and reversing it is deleting this one block.

   THE BREAKPOINTS ARE THE HOUSE'S OWN — the rail-aware pair above, 1200 with
   the rail collapsed and 1346 with it open, which is the step where
   --tile-span itself goes to three cards. BELOW THAT STEP NOTHING IS FORCED:
   the grid is back on auto-fill at --tile-min and steps down to two and then
   one, so a phone gets one card the width of its column. Three across on a
   430px screen would be 131px cards, which is not a card. */
@media (min-width:1200px){
body:has(.sidebar.collapsed) .project-grid{
    grid-template-columns:repeat(3, minmax(0, 1fr));
    max-width:var(--tile-span-3);
  }
}
@media (min-width:1346px){
.project-grid{
    grid-template-columns:repeat(3, minmax(0, 1fr));
    max-width:var(--tile-span-3);
  }
}
/* ===========================================================================
   NOTHING SHIFTS AFTER IT LOADS — the head of one project.  Row 1824.
   ===========================================================================

   The gap this row left open in September, named in its own note as still
   there: "the project page still scores 0.026–0.046 on #projSub and is
   untouched". It still did — 0.029, on one cold boot in three, and the detail
   run names all three things that move in the same frame:

     div.pj-head > div.pj-head-main > div.pj-progress   [146,108,320,5] -> [146,125,320,5]
     div.pj-head > div                                 [1211,44,227,101] -> [1158,44,280,101]
     div.pj-toolbar                                    [104,115,1304,75] -> [104,132,1304,75]

   One frame, one function: paintProjectHeader fills #projSub and #projStatusPill
   together. The first and third lines are the same 17 pixels — one line of
   .page-sub arriving in an element that was zero tall — carrying the progress
   bar, the toolbar and every task row under them down the page. The second is
   the pill: it is empty in the markup too, and the column it sits in is held
   against the right edge, so a word landing in it drags the whole toolbar 53
   pixels to the left.

   .pj-head-main ALREADY HAS flex-basis:0 — `flex:1` is `1 1 0%` — so the wrap
   half of the vault's fix was never the fault here, which is why this one looks
   different from the two above. Both halves of it are reservations.

   THE PILL'S FLOOR IS ITS OWN WIDEST WORD. PROJ_STATUS_LABEL has five values and
   no sixth is reachable: On track, At risk, Off track, On hold, Complete. Eight
   characters is the longest of them, so the floor is 8ch plus the pill's own two
   --space-3 of padding, in ch rather than pixels so that it follows the type it
   is holding instead of being a number measured once against a font that may not
   even have loaded. A floor cannot shorten a longer word; all it does is stop an
   empty pill being nothing wide while the project is still being read. */
#projSub{ min-height: calc(var(--type-body) * 1.35); }
#projStatusPill{ min-width: calc(8ch + var(--space-3) * 2); text-align:center; }
