/* ============================================================
   Design System — Components
   Faz 0: preview/index.html'den birebir taşındı (görsel-nötr).
   Faz 1'de kanonik modifier sözlüğüne (--_tint/--_fg/--_edge +
   re-c-{role} + -sm/-md/-lg) refactor edilecek.

   Bağımlılık: redev-ui-base.css (renk token'ları, --radius, font)
   ============================================================ */
/* v2 A20 — components live in the `components` cascade layer (after base/docs,
   before utilities), so utilities can override a component default at equal specificity. */
@layer components {

/* ════════════════════════════════════════════════════════════
   KANONİK MODİFİER SÖZLÜĞÜ (Faz 1) — DRY motoru

   Base component private rol değişkenlerini okur (--_tint/--_fg/
   --_edge/--_solid/--_text); re-c-{role} sadece bunları remap eder.
   → Her renk TEK tanım, TÜM componentlerde bedava çalışır.

   Kullanım:
     <span class="re-badge re-c-success">Aktif</span>
     <button class="re-btn re-btn-solid re-c-danger">Sil</button>
     <div class="re-alert re-c-warning">Dikkat</div>

   Size sözlüğü: -sm / (default) / -lg  → density-aware control-height
   ════════════════════════════════════════════════════════════ */
.re-c-primary   { --_solid:var(--primary);   --_fg:var(--primary-foreground);   --_text:var(--primary);   --_tint:color-mix(in oklch,var(--primary) 15%,transparent);   --_edge:color-mix(in oklch,var(--primary) 30%,transparent); }
.re-c-secondary { --_solid:var(--secondary); --_fg:var(--secondary-foreground); --_text:var(--secondary-foreground); --_tint:color-mix(in oklch,var(--secondary) 60%,transparent); --_edge:var(--border); }
.re-c-success   { --_solid:var(--success);   --_fg:var(--success-foreground);   --_text:var(--success);   --_tint:color-mix(in oklch,var(--success) 15%,transparent);   --_edge:color-mix(in oklch,var(--success) 30%,transparent); }
.re-c-warning   { --_solid:var(--warning);   --_fg:var(--warning-foreground);   --_text:color-mix(in oklab,var(--warning) 70%,var(--foreground)); --_tint:color-mix(in oklch,var(--warning) 15%,transparent); --_edge:color-mix(in oklch,var(--warning) 35%,transparent); }
.re-c-danger    { --_solid:var(--danger);    --_fg:var(--danger-foreground);    --_text:var(--danger);    --_tint:color-mix(in oklch,var(--danger) 15%,transparent);    --_edge:color-mix(in oklch,var(--danger) 30%,transparent); }
.re-c-info      { --_solid:var(--info);      --_fg:var(--info-foreground);      --_text:var(--info);      --_tint:color-mix(in oklch,var(--info) 15%,transparent);      --_edge:color-mix(in oklch,var(--info) 30%,transparent); }
.re-c-accent    { --_solid:var(--accent);    --_fg:var(--accent-foreground);    --_text:var(--accent);    --_tint:color-mix(in oklch,var(--accent) 15%,transparent);    --_edge:color-mix(in oklch,var(--accent) 30%,transparent); }
.re-c-muted     { --_solid:color-mix(in oklab,var(--foreground) 68%,var(--muted-foreground)); --_fg:var(--background); --_text:var(--muted-foreground); --_tint:color-mix(in oklch,var(--muted-foreground) 12%,transparent); --_edge:var(--border); }

/* ── Unified Button (re-btn) — density-aware, re-c-{role} destekli ── */
.re-btn{--_h:var(--control-h-md);--_px:var(--pad-x);--_fs:calc(0.8125rem * var(--font-scale));display:inline-flex;align-items:center;justify-content:center;gap:var(--re-space-2);height:var(--_h);padding:0 var(--_px);border-radius:var(--radius-control);border:1px solid transparent;font-size:var(--_fs);font-weight:500;font-family:inherit;cursor:pointer;transition:opacity .15s,background .15s;white-space:nowrap;background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground))}
.re-btn:disabled{opacity:.5;cursor:not-allowed}
/* Size is THREE numbers, and they are now a token set rather than three declarations copied
   per variant. Reason: a container sometimes has to set the size of what it holds — a crowded
   re-ph-actions slot drops to `-sm` on its own (see there) — and the only ways to do that
   without a token set are to re-declare -sm's three values in a second place, or to remap
   --control-h-md and get a short button with medium type. Both are the mistake this file keeps
   being caught by. --_h/--_px/--_fs live in the same `--_*` namespace the colour dictionary
   uses (--_solid/--_fg/--_text), so a container sets a size the way it sets a role.
   Verified byte-for-byte against the previous rules: md 42/18/13, sm 32/12.96/12,
   lg 52/23.4/14 at standard density. */
.re-btn-sm{--_h:var(--re-ctl-sm-h);--_px:var(--re-ctl-sm-px);--_fs:var(--re-ctl-sm-fs)}
.re-btn-lg{--_h:var(--control-h-lg);--_px:calc(var(--pad-x) * 1.3);--_fs:calc(0.875rem * var(--font-scale))}
/* The button never sized the icon it hosts: the size lived in the markup, `width="14"`
   repeated 255 times, and -sm / -lg drew the SAME glyph as the default. One rule reading
   the ratio makes all three sizes follow the button's own type (13 / 12 / 14px → 15 / 13.8
   / 16.1px), which is what the 14 was approximating. A presentation attribute loses to CSS,
   so the existing markup needs no edit. */
.re-btn svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
/* Icon-only square button — combine with a variant (re-btn-outline/-ghost) for the look.
   The width reads --_h, the same token the HEIGHT reads, so "square" is one value and not an
   agreement between two. It used to name --control-h-md with -sm/-lg overriding it, which was
   fine while size came only from a class — and stopped being fine the moment a CONTAINER could
   set the size: re-ph-actions' size-by-count rule sets --_h, so a crowded slot gave a 32px-tall
   button a 42px width. Measured 42.0×32.0 before, 32.0×32.0 after; the -sm/-lg pairs are gone
   because those classes already set --_h. */
.re-btn-icon{width:var(--_h);padding:0;flex-shrink:0}
/* v7 §V7-10 — a button wrapping an avatar. Two scales collide here: re-avatar is a fixed
   pixel size while --control-h-* is density-aware, so a plain icon button and its avatar
   do not agree — measured at compact density the avatar OVERFLOWED the button at all
   three sizes (re-btn-lg 36.8px vs re-avatar-lg 48px), turning the hover ring inside out.
   Sizing the button from the avatar instead would fix that but break the rule that
   matters more: two controls on the same row must measure the same, and this button sits
   next to plain icon buttons in a topbar. So the BUTTON keeps the control height and the
   AVATAR is fitted into it — the inset is then exact at every density and every avatar
   size. Radius: a round avatar wants a round ring; a square one wants
   outer = inner + inset + the 1px border, which is what concentric means for a rounded
   box (miss the border and it is 1px off — visible as a "flatter" corner, not as a bug).
   Naming, because two consumers measured two different numbers off this rule: the INSET is
   the padding (2px). The gap you can SEE is inset + the 1px border = 3px, and that is what
   the radius formula adds. So --re-btn-avatar-inset:2px and a measured gap of 3px are the
   same thing described from two sides, not a 1px error.
   Must stay AFTER .re-btn-icon: same specificity, so source order decides. */
.re-btn-avatar{width:var(--control-h-md);padding:var(--re-btn-avatar-inset,2px);border-radius:var(--radius-full);flex-shrink:0}
.re-btn-sm.re-btn-avatar{width:var(--control-h-sm)}
.re-btn-lg.re-btn-avatar{width:var(--control-h-lg)}
.re-btn-avatar > .re-avatar{width:100%;height:100%}
/* Concentric with the avatar's REAL corner, not with --radius-md. The avatar caps its radius
   (0.75rem, and 43% of its own side); this rule used the uncapped token, so at data-radius lg /
   full the ring was 21 / 27px on a 34.4px button — clamped into a CIRCLE behind a squircle avatar
   (roundness 1.00 vs 0.85). A `%` here resolves against the BUTTON, and the avatar is the button's
   content box, so the avatar's 43% of (W − 2g) is 43% of W − 0.86g, g = inset + the 1px border.
   Measured shape, button / avatar: md 0.87 / 0.85 (unchanged), lg and full 1.00 → 0.87, compact
   1.00 / 1.00 (both round) → 0.89 / 0.86. */
.re-btn-avatar:has(> .re-avatar-square){border-radius:calc(min(var(--radius-md),0.75rem,calc(43% - 0.86 * (var(--re-btn-avatar-inset,2px) + 1px))) + var(--re-btn-avatar-inset,2px) + 1px)}
.re-btn-soft{background:var(--_tint,color-mix(in oklch,var(--foreground) 7%,transparent));color:var(--_text,var(--foreground));border-color:var(--_edge,color-mix(in oklch,var(--foreground) 18%,transparent))}
.re-btn-outline{background:transparent;color:var(--_text,var(--foreground));border-color:var(--_edge,var(--border))}
.re-btn-ghost{background:transparent;color:var(--_text,var(--foreground));border-color:transparent}
/* SELECTED — an unfilled button used as a filter or a switch (see Foundations → Filter &
   Switch, which offers re-btn "when you need an icon or a wider hit area"). reFnActive writes
   .active + aria-pressed on whatever control it is given, and for a button the DS drew
   nothing back: the state existed in the DOM and not on screen, so a union filter looked
   broken — two selected tags show both rows, which reads as "my click did nothing" when
   neither tag looks selected. Same tint as re-chip.active, deliberately: one selected look.
   Only the UNFILLED variants — a solid button already reads as on, and tinting it would be
   a second, quieter "on" competing with its own fill. */
.re-btn-outline.active,.re-btn-outline[aria-pressed="true"],
.re-btn-ghost.active,.re-btn-ghost[aria-pressed="true"]{background:var(--_tint,color-mix(in oklch,var(--primary) 15%,transparent));color:var(--_text,var(--primary));border-color:var(--_edge,color-mix(in oklch,var(--primary) 30%,transparent))}
/* Hover — solid darkens (light) / brightens (dark) via foreground mix; soft/outline/ghost gain a role fill. Order: base first, variants override. */
.re-btn:hover:not(:disabled){background:color-mix(in oklab,var(--_solid,var(--primary)) 88%,var(--foreground))}
.re-btn-soft:hover:not(:disabled){background:color-mix(in oklch,var(--_solid,var(--primary)) 22%,transparent)}
.re-btn-outline:hover:not(:disabled){background:color-mix(in oklch,var(--_solid,var(--primary)) 12%,transparent)}
.re-btn-ghost:hover:not(:disabled){background:color-mix(in oklch,var(--_solid,var(--primary)) 12%,transparent)}
/* Text button — no box at all: the label carries an underline that draws in from the left on
   hover (UIkit's uk-button-text). Reach for it where a filled/bordered control would crowd
   its surroundings — an re-section-head action, an inline "All ›". The 3px bottom padding
   sits the rule a touch below a normal text-decoration underline. */
.re-btn-text{height:auto;padding:0 0 3px;border:none;border-radius:0;background:none;color:var(--_text,var(--foreground));position:relative}
.re-btn-text::after{content:"";position:absolute;left:0;right:100%;bottom:0;border-bottom:1px solid currentColor;transition:right .25s ease-out}
/* A text action beside a form label (v46, calendar: "Zaman · Bitiş günü ekle"). Its own 18px line and the 3px pad its
   underline sweeps in made the label row 21px instead of 15, so that field sat 3px further from its label than the
   others (11 vs 8). Beside a label it takes the label's line and size, and the underline sits on the text's own
   baseline box. */
.re-input-label .re-btn-text{line-height:inherit;font-size:inherit;padding-bottom:0}
.re-btn-text:hover:not(:disabled),.re-btn-text:focus-visible{background:none;color:var(--_solid,var(--primary))}
.re-btn-text:hover:not(:disabled)::after,.re-btn-text:focus-visible::after{right:0}

/* ── Unified Badge (re-badge) — re-c-{role} destekli ── */
.re-badge{display:inline-flex;align-items:center;gap:var(--re-space-1);padding:var(--re-space-1) var(--re-space-2);border-radius:var(--radius-full);font-size:calc(0.6875rem * var(--font-scale));font-weight:500;line-height:1;border:1px solid var(--_edge,transparent);background:var(--_tint,color-mix(in oklch,var(--muted-foreground) 12%,transparent));color:var(--_text,color-mix(in oklch,var(--muted-foreground),var(--foreground) 25%))}
/* The neutral badge's text is muted-foreground moved a quarter toward the foreground: plain muted-foreground on its own 12% tint
   read 4.16:1 at 10px (Lighthouse, landing family page, 2026-10-07); this reads 6.02 light and 6.3–6.9 dark. A role badge keeps --_text. */
.re-badge-solid{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));border-color:transparent}
.re-badge-sm{font-size:calc(0.625rem * var(--font-scale));padding:1px var(--re-space-1)}
.re-badge-lg{font-size:calc(0.75rem * var(--font-scale));padding:var(--re-space-1) var(--re-space-3)}
/* reMemberBadges' name-side group: beside the member's name, inline, one gap from it (IV1). */
.re-member-tags{display:inline-flex;align-items:center;gap:var(--re-space-1);margin-left:var(--re-space-2);vertical-align:middle}

/* ── Unified Tag (re-tag) — kare köşeli badge, re-c-{role} ── */
.re-tag{display:inline-flex;align-items:center;gap:var(--re-space-1);padding:var(--re-space-1) var(--re-space-2);border-radius:var(--radius-inset);font-size:calc(0.6875rem * var(--font-scale));font-weight:500;line-height:1;background:var(--_tint,color-mix(in oklch,var(--muted-foreground) 12%,transparent));color:var(--_text,var(--muted-foreground));border:1px solid var(--_edge,var(--border))}
.re-tag-solid{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));border-color:transparent}

/* ── Chip (re-chip) — interactive filter pill, re-c-{role}. active = tint pill ── */
.re-chip-group{display:flex;flex-wrap:wrap;gap:var(--re-space-2);align-items:center}
.re-chip-group-label{font-size:calc(0.75rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);margin-right:calc(var(--re-space-1))}
/* framed filter bar — label + chips in a bordered surface (like a form control) */
/* The frame is a pill, like everything inside it. A rectangle around a row of pills reads as
   two different shape languages in one control; --radius-full clamps to half the height, so a
   bar that wraps to two lines becomes a stadium rather than a lozenge. (That is the surface
   caveat on the Radius page, and it does not apply here: this is a control, not a card.) */
.re-chip-bar{border:1px solid var(--border);border-radius:var(--radius-full);background:var(--card);padding:var(--re-space-2) var(--re-space-4)}
.re-chip{display:inline-flex;align-items:center;gap:var(--re-space-1);padding:calc(var(--re-space-1) + 1px) var(--re-space-3);border-radius:var(--radius-full);font-size:calc(0.75rem * var(--font-scale));font-weight:500;line-height:1;cursor:pointer;user-select:none;background:var(--card);color:var(--foreground);border:1px solid var(--border);transition:background .12s,border-color .12s,color .12s}
.re-chip:hover{border-color:var(--_solid,var(--primary));color:var(--_text,var(--primary))}
.re-chip:focus-visible{outline:2px solid var(--c-focus,var(--ring));outline-offset:2px}
.re-chip.active,.re-chip[aria-pressed="true"]{background:var(--_tint,color-mix(in oklch,var(--primary) 15%,transparent));color:var(--_text,var(--primary));border-color:var(--_edge,color-mix(in oklch,var(--primary) 30%,transparent))}
.re-chip-solid.active,.re-chip-solid[aria-pressed="true"]{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));border-color:transparent}
/* ── Filter bar (re-filter-bar) — toolbar above a table/list ──
   Left: search / selects / chips. Right: result count (re-filter-result, pushed
   right with margin-left:auto). re-filter-bar-framed for a bordered surface. */
.re-filter-bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--re-space-2)}
.re-filter-bar-framed{border:1px solid var(--border);border-radius:var(--radius-full);background:var(--card);padding:var(--re-space-2) var(--re-space-4)}   /* same pill frame as re-chip-bar */
/* Everything at the right end, in markup order. The default bar leads with its controls and
   pushes the count away with re-filter-result's margin-left:auto; this one is for the other
   shape — a small control and its count as one group, opposite the heading. The auto margin
   has to go or it would still split the pair. */
.re-filter-bar-end{justify-content:flex-end}
.re-filter-bar-end > .re-filter-result{margin-left:0}
.re-filter-result{margin-left:auto;display:inline-flex;align-items:center;gap:.3em;padding:2px 10px;border-radius:var(--radius-full);border:1px solid color-mix(in oklch,var(--primary) 30%,transparent);background:color-mix(in oklch,var(--primary) 12%,transparent);font-size:calc(0.6875rem * var(--font-scale));font-weight:500;color:var(--primary);white-space:nowrap}
.re-filter-search{position:relative;display:inline-flex;align-items:center}
.re-filter-search svg{position:absolute;left:9px;color:var(--muted-foreground);pointer-events:none}
.re-filter-search input{height:var(--control-h-sm);padding:0 10px 0 28px;border-radius:var(--radius-control);border:1px solid var(--border);background:var(--surface);color:var(--foreground);font-size:calc(0.78rem * var(--font-scale));font-family:inherit;outline:none;width:200px}
.re-filter-search input:focus{border-color:var(--primary)}

/* ── Unified Alert (re-alert) — density-aware, re-c-{role} ── */
.re-alert{display:flex;gap:var(--re-space-2);padding:var(--pad-y) var(--pad-x);border-radius:var(--radius-md);border-left:3px solid var(--_solid,var(--info));background:var(--_tint,color-mix(in oklch,var(--info) 10%,transparent));font-size:calc(0.8125rem * var(--font-scale));line-height:1.5;color:var(--foreground)}
.re-alert .re-alert-icon{width:16px;height:16px;flex-shrink:0;margin-top:1px;color:var(--_solid,var(--info))}
/* Titled variant: icon + a stacked title/body block (single-line usage unchanged) */
.re-alert-body{display:flex;flex-direction:column;gap:calc(3px * var(--density-mult,1));flex:1;min-width:0}
.re-alert-title{font-weight:600;color:var(--foreground);line-height:1.35}

/* ── Unified Input (re-input) — density-aware height ── */
.re-input{width:100%;height:var(--control-h-md);padding:0 var(--re-box-inset);border-radius:var(--radius-control);border:1px solid var(--border);background:var(--card);color:var(--foreground);font-size:calc(0.8125rem * var(--font-scale));font-family:inherit;outline:none;transition:border-color .15s}
.re-input::placeholder{color:var(--muted-foreground)}
.re-input:focus{border-color:var(--primary)}
.re-input-sm{height:var(--control-h-sm);font-size:calc(0.75rem * var(--font-scale))}
.re-input-lg{height:var(--control-h-lg);font-size:calc(0.875rem * var(--font-scale))}

/* ── re-float — the label starts as the placeholder and stays as a label ───────
   labs v13 §V13-3, and the user picked the pattern (`A-34`): empty field shows the text
   where a placeholder would be; type into it and the same text rises and stays inside the
   box, above the value.

   ⚠ It is a real <label>, not a placeholder — that is the whole reason the variant exists.
   A placeholder is not an accessible name and it leaves the moment typing starts, so the
   field a sighted user can still identify becomes anonymous to a screen reader exactly when
   it has content. Here the accessible name is the <label for>; `placeholder=" "` (one space)
   is present only so :placeholder-shown can report "empty".

   No JS: :placeholder-shown answers "is it empty" and :focus answers "is it being used".

     <div class="re-float">
       <input class="re-input" id="email" placeholder=" ">
       <label class="re-float-label" for="email">Email</label>
     </div>

   The box is --control-h-lg, not -md: the raised label and the value need two lines of room
   inside one control, and a 42px box cannot hold both without the value colliding with the
   label. */
.re-float{position:relative}
.re-float > .re-input{
  height:var(--control-h-lg);
  /* the value sits BELOW the raised label, so the top padding is not symmetric */
  padding-top:calc(1.05em + 4px);padding-bottom:4px;
}
.re-float > .re-float-label{
  position:absolute;left:var(--re-box-inset);top:50%;transform:translateY(-50%);
  transform-origin:left center;
  font-size:calc(0.8125rem * var(--font-scale));   /* the input's size, so it reads as a placeholder */
  line-height:1;color:var(--muted-foreground);
  pointer-events:none;                              /* clicks belong to the field */
  transition:transform .15s ease, font-size .15s ease, color .15s ease;
  max-width:calc(100% - 2 * var(--re-box-inset));overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* raised: the field has content, or is being typed into */
/* only the transform here — colour stays the base label's, so the :focus rule below is the
   single place that changes it. Declaring it in both had the focus case setting `color`
   twice, which check:css flagged. */
.re-float > .re-input:not(:placeholder-shown) + .re-float-label,
.re-float > .re-input:focus + .re-float-label{
  transform:translateY(calc(-50% - 0.62em)) scale(0.8);
}
.re-float > .re-input:focus + .re-float-label{color:var(--primary)}
/* a select or textarea can carry the same pattern; a select is never "placeholder-shown", so
   an empty option value drives it instead */
.re-float > .re-select{height:var(--control-h-lg);padding-top:calc(1.05em + 4px);padding-bottom:4px}
.re-float > .re-select:not([data-empty]) + .re-float-label,
.re-float > .re-select:focus + .re-float-label{transform:translateY(calc(-50% - 0.62em)) scale(0.8)}
.re-input.error,.re-input[aria-invalid="true"],.re-textarea[aria-invalid="true"],.re-select[aria-invalid="true"]{border-color:var(--danger)}  /* v9 — aria-invalid alongside .error, because a consumer marking a field invalid for a screen reader was having to write the colour themselves: the class carries the look, the attribute carries the meaning, and only one of them reaches assistive tech. Pair either with re-input-error for the message. */
.re-input-label{display:flex;flex-direction:column;gap:calc(6px * var(--density-mult,1));min-width:0;font-size:calc(0.75rem * var(--font-scale));font-weight:500;color:var(--foreground);margin-bottom:calc(6px * var(--density-mult,1))}
/* v10 — the label is a flex column with a gap instead of naming its children. The old form
   listed three control classes (> .re-input, > .re-select, > .re-textarea) and two controls added
   since — re-money-input in v0.7.0 and re-search-wrap — were never added to it, so both stuck to
   the label text and a consumer carried local CSS for them. Same shape as v8 §V8-3: a structure
   assumed inside a fixed selector, silent when the assumption stops holding.
   A gap also fixes what a :not(:first-child) rewrite could not: in <label>Text<input></label> the
   label text is a TEXT NODE, so the control IS the first element child — measured, that version
   gave 0px. In a flex column the text becomes an anonymous item and the gap lands between them
   whichever way the label is written. */
  /* v2 §2.8 — when the control is nested INSIDE the label (a11y), the label's margin-bottom sits below the block; this spaces the label text from the control */
.re-input-helper{font-size:calc(0.6875rem * var(--font-scale));color:var(--muted-foreground);margin-top:calc(3px * var(--density-mult,1))}
.re-input-error{font-size:calc(0.6875rem * var(--font-scale));color:var(--danger);margin-top:calc(3px * var(--density-mult,1))}

/* v6 V6-7 — native tarih/saat denetimi kolonu taşırıyor (iOS Safari) ─────────────────
   Belirti: `re-cols-2` veya `flex:1` içindeki iki tarih alanı ekrandan taşıyor, ya da
   yan yana birleşik görünüyor (gap yokmuş gibi).
   Sebep İKİ KATMANLI ve ilk turda yalnız birincisi görüldü:
     (1) TRACK — grid'de `1fr` aslında `minmax(auto,1fr)`; track öğenin min-content
         genişliğinden küçülemiyor (flex'te aynısı `min-width:auto` ile olur).
     (2) ÖĞE — iOS Safari `input[type="date"]`i KENDİ intrinsic genişliğinde çiziyor ve
         `width:100%` ile küçülmüyor. Yani track küçülse bile öğe taşıyor.
   ⚠ Yalnız (1)'i düzeltmek sorunu Chrome'da GİZLER, Safari'de bırakır — headless
   Chrome'un tarih denetimi uslu küçüldüğü için ölçüm iki tur boyunca temiz çıktı.
   `appearance:none` denetimi standart kutuya çeviriyor; dokununca native seçici YİNE açılır. */
.re-input[type="date"],.re-input[type="time"],.re-input[type="datetime-local"],
.re-input[type="month"],.re-input[type="week"]{
  -webkit-appearance:none;appearance:none;
  width:100%;min-width:0;max-width:100%;
  height:var(--control-h-md);font-family:inherit}
.re-input-sm[type="date"],.re-input-sm[type="time"],.re-input-sm[type="datetime-local"]{height:var(--control-h-sm)}
.re-input-lg[type="date"],.re-input-lg[type="time"],.re-input-lg[type="datetime-local"]{height:var(--control-h-lg)}
/* WebKit'in değer kutusu kendi intrinsic genişliğini taşıyor — kapsayıcıya uydur */
.re-input::-webkit-date-and-time-value{width:100%;min-width:0;text-align:left}
.re-input::-webkit-calendar-picker-indicator{margin-inline-start:auto;opacity:.55;cursor:pointer}

/* ── Checkbox / Radio — native accent-color, minimal + accessible ── */
.re-checkbox,.re-radio{width:16px;height:16px;accent-color:var(--primary);cursor:pointer;flex-shrink:0;margin:0}
.re-radio{border-radius:50%}
.re-checkbox:disabled,.re-radio:disabled{cursor:not-allowed;opacity:.5}
/* Label wrapper (control + text) */
.re-check{display:inline-flex;align-items:center;gap:var(--re-space-2);cursor:pointer;font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground)}
.re-check:has(:disabled){cursor:not-allowed;color:var(--muted-foreground)}

/* ── Switch — boolean toggle (CSS-only) ── */
.re-switch{position:relative;display:inline-block;width:38px;height:22px;flex-shrink:0;cursor:pointer}
.re-switch input{position:absolute;opacity:0;width:0;height:0;margin:0}
.re-switch-track{position:absolute;inset:0;background:var(--border);border-radius:999px;transition:background .2s}
/* Knob is the track's ::after — no separate element, so it can't be mis-nested. */
.re-switch-track::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;background:var(--knob);border-radius:50%;transition:transform .2s;box-shadow:var(--shadow-sm)}
.re-switch input:checked ~ .re-switch-track{background:var(--primary)}
.re-switch input:checked ~ .re-switch-track::after{transform:translateX(16px)}
.re-switch input:disabled ~ .re-switch-track{opacity:.5}
.re-switch input:focus-visible ~ .re-switch-track{outline:2px solid var(--c-focus);outline-offset:2px}
/* Deprecated: knob now lives on .re-switch-track::after. Old markup with an
   explicit .re-switch-knob element stays valid (hidden here, ::after draws it). */
.re-switch-knob{display:none}

/* ── Range slider (re-slider) — styled <input type="range">, re-c-{role} thumb ── */
.re-slider{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:999px;background:var(--muted);cursor:pointer;outline:none}
.re-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--_solid,var(--primary));border:2px solid var(--card);box-shadow:var(--shadow-sm);cursor:pointer;transition:transform .1s}
.re-slider::-webkit-slider-thumb:active{transform:scale(1.15)}
.re-slider::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--_solid,var(--primary));border:2px solid var(--card);box-shadow:var(--shadow-sm);cursor:pointer}
.re-slider:focus-visible{outline:2px solid var(--c-focus);outline-offset:4px}
.re-slider:disabled{opacity:.5;cursor:not-allowed}
.re-slider-sm{height:4px}
.re-slider-sm::-webkit-slider-thumb{width:12px;height:12px}.re-slider-sm::-moz-range-thumb{width:12px;height:12px}

/* ── Rating (re-rating) — star scale; JS toggles .on up to the chosen star ── */
.re-rating{display:inline-flex;gap:2px;color:var(--border)}
.re-rating-star{background:none;border:none;padding:0;cursor:pointer;font-size:1.25rem;line-height:1;color:inherit;transition:color .1s,transform .1s}
.re-rating-star.on{color:var(--warning)}
.re-rating-star:hover{transform:scale(1.12)}
.re-rating-star:focus-visible{outline:2px solid var(--c-focus);outline-offset:2px;border-radius:4px}
.re-rating.re-rating-readonly .re-rating-star{cursor:default}
.re-rating.re-rating-readonly .re-rating-star:hover{transform:none}

/* ── Popover (re-popover) — click-triggered floating panel with arbitrary content
   (richer than a menu dropdown, more than a hover tooltip). JS toggles .open. ── */
.re-popover{position:relative;display:inline-block}
.re-popover-panel{position:absolute;top:calc(100% + 8px);left:0;z-index:300;min-width:220px;max-width:320px;background:var(--card);border:1px solid var(--border-elevated);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);padding:var(--pad-y) var(--pad-x);font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground);display:none}
.re-popover-panel.open{display:block}
.re-popover-panel.re-popover-end{left:auto;right:0}
.re-popover-title{font-weight:600;margin:0 0 4px}

/* ── Tree (re-tree) — collapsible hierarchy (files, nav, taxonomy). JS toggles
   .open on an .re-tree-branch; leaves are plain .re-tree-node. ── */
.re-tree{font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground)}
.re-tree-branch{position:relative}
/* Nav-shaped: [icon][label][chevron→right]. Indent via left padding (per --lvl, step
   17px) so the hover band is full-width & left-aligned. Supports arbitrary depth. */
.re-tree-node{display:flex;align-items:center;gap:8px;min-height:30px;width:100%;padding:4px 10px 4px calc(10px + var(--lvl,0) * 17px);border-radius:var(--radius-inset);cursor:pointer;background:none;border:none;font-family:inherit;font-size:inherit;color:inherit;text-align:left;transition:background .12s}
.re-tree-node:hover{background:color-mix(in oklch,var(--foreground) 6%,transparent)}
.re-tree-node.active{background:color-mix(in oklch,var(--primary) 12%,transparent);color:var(--primary)}
.re-tree-icon{width:16px;height:16px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--muted-foreground)}
.re-tree-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-tree-toggle{width:14px;height:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--muted-foreground);transition:transform .15s}
.re-tree-branch.open > .re-tree-node .re-tree-toggle{transform:rotate(90deg)}
.re-tree-children{display:none}
.re-tree-branch.open > .re-tree-children{display:block}
/* Guide line: a left rail under each open branch's icon, beside its children */
.re-tree-branch.open::before{content:'';position:absolute;left:calc(10px + var(--lvl,0) * 17px + 8px);top:32px;bottom:2px;width:1px;background:var(--border)}

/* ── Split panel (re-split) — resizable panes with a draggable divider.
   JS (initSplit) drags .re-split-divider to resize the preceding pane. ── */
.re-split{display:flex;width:100%;overflow:hidden;border:1px solid var(--border-surface);border-radius:var(--radius-md)}
.re-split-vertical{flex-direction:column}
.re-split-pane{overflow:auto;min-width:0;min-height:0}
.re-split-pane:first-child{flex:0 0 40%}
.re-split-pane:last-child{flex:1 1 0}
.re-split-divider{flex:0 0 6px;background:var(--border);cursor:col-resize;transition:background .12s;touch-action:none}
.re-split-divider:hover{background:color-mix(in oklab,var(--primary) 55%,var(--border))}
.re-split-vertical > .re-split-divider{cursor:row-resize}

/* ── Textarea — multiline input ── */
.re-textarea{width:100%;min-height:80px;padding:var(--pad-y) var(--pad-x);border-radius:var(--radius-md);border:1px solid var(--border);background:var(--card);color:var(--foreground);font-size:calc(0.8125rem * var(--font-scale));font-family:inherit;line-height:1.5;outline:none;resize:vertical;transition:border-color .15s}
.re-textarea::placeholder{color:var(--muted-foreground)}

/* ── re-prose — rendered rich text inside an app (v7 §V7-3) ──────────────────────────
   re-textarea is the EDIT side of a description; this is the read side. Markdown (or any
   HTML you render) needs typographic rules somewhere, and without them a saved description
   collapses into one run of unspaced tags. The docs site has its own prose in the `docs`
   layer, but that one is tuned for long-form article pages — this is the compact version an
   app pane wants.
   Spacing is the owl selector (> * + *) rather than per-element margins: it cannot double
   up, it cannot collapse, and it needs no :first-child/:last-child reset. Element selectors
   keep specificity at 0,1,1 so a consumer's own class overrides any of it without
   !important. */
.re-prose{font-size:calc(var(--re-prose-size,0.8125rem) * var(--font-scale));line-height:1.6;color:var(--foreground);
  overflow-wrap:break-word}
/* Size lives in a knob, not just in two variants, because prose takes its scale from what
   surrounds it — a chat bubble, a card, a side panel — and that is a continuum rather than
   three steps. A consumer whose bubbles are deliberately 0.875rem had to skip re-prose
   entirely, since adopting it shrank their typography. The -sm/-lg variants set the same
   knob so the modifier dictionary still works. */
.re-prose-sm{--re-prose-size:0.75rem}
.re-prose-lg{--re-prose-size:0.875rem}
/* Reset FIRST and inside :where(), then the owl. Order and :where() both matter: a plain
   .re-prose p is 0,1,1 and would beat the owl's 0,1,0 — measured, every p→ul and
   ul→blockquote gap came out 0px while the heading rules (also 0,1,1) worked, which is what
   gave the game away. :where() drops the reset to 0,1,0 and putting it first lets the owl win.
   font-size/line-height are re-stated as inherit because a host page may style `p` directly
   (this docs site does); the container's own value would otherwise never reach the text. */
.re-prose :where(p,ul,ol,blockquote,pre,li,td,th){margin:0;font-size:inherit;line-height:inherit}
.re-prose :where(h1,h2,h3,h4,h5,h6){margin:0;font-weight:650;line-height:1.3;color:var(--foreground)}  /* h1/h2 included even though the scale below only styles h3/h4: rendered markdown may well contain them, and an unreset h2 arrives with the UA's 0.83em margin. A note's own title is re-note-title, so body headings should start at h3 — but the reset must not depend on the consumer knowing that. */
.re-prose > * + *{margin-top:var(--re-space-2)}
.re-prose h3{font-size:1.1538em}   /* em, NOT rem: the headings have to follow --re-prose-size, or re-prose-lg would grow the body and leave the headings behind. --font-scale is already in the container's computed size, so em inherits it once rather than multiplying twice. */
.re-prose h4{font-size:1.0769em}
.re-prose h1{font-size:1.3846em}
.re-prose h2{font-size:1.2308em}
.re-prose :where(h5,h6){font-size:1em;color:var(--muted-foreground)}
.re-prose > :is(h1,h2,h3,h4,h5,h6) + *{margin-top:var(--re-space-1)}
.re-prose > * + :is(h1,h2,h3,h4,h5,h6){margin-top:var(--re-space-4)}
.re-prose a{color:var(--primary);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px}
.re-prose strong{font-weight:650}
.re-prose ul,.re-prose ol{padding-left:1.35em;display:flex;flex-direction:column;gap:2px}
.re-prose li::marker{color:var(--muted-foreground)}
/* Inline code must not change the line box — padding-block on an inline element would push
   the lines around it apart. Hence 0 vertical padding and a background only. */
.re-prose code{padding:0 .32em;border-radius:var(--radius-inset);background:var(--muted);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em}
.re-prose pre{padding:var(--re-space-3);border-radius:var(--radius-md);background:var(--muted);
  overflow-x:auto;overflow-y:auto;   /* both axes stated: one axis on auto silently promotes the other, so write the intent rather than inheriting it — and this one genuinely wants vertical scroll if a consumer caps the height */font-size:.92em}
.re-prose pre code{padding:0;background:none;font-size:1em}
.re-prose blockquote{padding-left:var(--re-space-3);border-left:2px solid var(--border);
  color:var(--muted-foreground)}
.re-prose hr{margin:var(--re-space-4) 0;border:0;border-top:1px solid var(--border)}
.re-prose img{max-width:100%;height:auto;border-radius:var(--radius-md)}
/* Reading measure. Off by default because a description pane is usually narrow already;
   add re-prose-measure where the pane can get wide. */
.re-prose-measure{max-width:68ch}
.re-textarea:focus{border-color:var(--primary)}

/* ── Select — native select styled like re-input + chevron ── */
.re-select{width:100%;height:var(--control-h-md);padding:0 calc(var(--pad-x) + 20px) 0 var(--pad-x);border-radius:var(--radius-control);border:1px solid var(--border);background:var(--card);color:var(--foreground);font-size:calc(0.8125rem * var(--font-scale));font-family:inherit;outline:none;cursor:pointer;transition:border-color .15s;
  appearance:none;-webkit-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23888888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right var(--pad-x) center;background-size:14px}
.re-select:focus{border-color:var(--primary)}
.re-select-sm{height:var(--control-h-sm);font-size:calc(0.75rem * var(--font-scale))}
.re-select-lg{height:var(--control-h-lg);font-size:calc(0.875rem * var(--font-scale))}

/* ── Breadcrumb ── */
.re-breadcrumb{display:flex;align-items:center;gap:var(--re-space-2);font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground);flex-wrap:wrap}
.re-breadcrumb a{color:var(--muted-foreground);text-decoration:none}
.re-breadcrumb a:hover{color:var(--foreground)}
.re-breadcrumb-sep{color:color-mix(in oklch,var(--muted-foreground) 45%,transparent)}
.re-breadcrumb-current{color:var(--foreground);font-weight:500}
/* In the shell's navbar on a phone (the shell's drawer width) the trail is the app's name alone: the page header right
   under the bar already names the screen, and the full trail broke onto two lines ("Plan / Etkinlikler / <event>",
   measured on an iPhone). Only a trail that marks the app's ring (reFamilyNav) is shortened; a hand-built one is left whole. */
@media (max-width:860px){
  .re-navbar .re-breadcrumb:has(> .re-breadcrumb-app){flex-wrap:nowrap;min-width:0}
  .re-navbar .re-breadcrumb:has(> .re-breadcrumb-app) > :not(.re-breadcrumb-app){display:none}
  .re-navbar .re-breadcrumb > .re-breadcrumb-app{color:var(--foreground);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
}

/* ── Avatar (+ group) ── */
.re-avatar{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;background:color-mix(in oklab,var(--primary) 18%,var(--card));color:var(--primary);font-size:13px;font-weight:600;overflow:hidden;flex-shrink:0;border:1px solid color-mix(in oklab,var(--primary) 22%,var(--card))}  /* v3.1 — opaque tint (over --card, not transparent) so overlapping avatars in a group don't bleed through each other */
.re-avatar img{width:100%;height:100%;object-fit:cover}
/* The fourth instance of one hole: re-row-lead, re-row-trailing and re-sidebar-toggle all
   record it — the slot was sized and its icon was not. An <svg> with no width here drew 26px
   in a 28px tile and 34 in a 36, edge to edge, and `--re-icon` cannot help: that token is
   declared ON the svg, so an avatar reading it gets nothing.
   The ratio is of the TILE, because this glyph is the box's content, not a mark sitting beside
   text. 0.5 lands where the DS's text-relative rule would anyway: re-avatar-sm is font-size 11,
   so 1em x --re-icon-ratio is 12.65px and half the tile is 13.0px. Two systems, 0.35px apart. */
.re-avatar > svg{width:calc(var(--re-avatar-glyph,.5) * 100%);height:calc(var(--re-avatar-glyph,.5) * 100%);flex-shrink:0}
.re-avatar-sm{width:28px;height:28px;font-size:11px}
/* 20px = re-person-sm's dot, measured. "An avatar on its own is re-avatar, not a person chip
   with the name removed" is only true if the circle is the SAME circle — and it was not: the
   chip's dot is 20px and the smallest avatar was 28px, so the two sat side by side in one
   list at two sizes. */
.re-avatar-xs{width:20px;height:20px;font-size:9px}
.re-avatar-lg{width:48px;height:48px;font-size:16px}
.re-avatar-xl{width:72px;height:72px;font-size:24px}    /* a profile page's identity avatar */
.re-avatar-2xl{width:96px;height:96px;font-size:32px}   /* the same, one step up. Both keep the scale's letter ratio: 16 at 48 */
.re-avatar-square{border-radius:min(var(--radius-md),0.75rem,43%)}
/* xl and 2xl lift the 0.75rem cap. That cap was chosen for 28–40px boxes, and on a 72 or 96px
   profile avatar it draws a sharp tile — roundness 0.33 and 0.25 — beside a family that goes 0.86
   at 28, 0.67 at 36 and 0.50 at 48. Both are written to stay at 0.50, the SHAPE of re-avatar-lg:
   1.5 x --radius-md at 72 and 2 x at 96, 18 and 24px at the default step. They still follow
   data-radius downward, and 25% of the side bounds them, so no knob step rounds them into a
   circle. The 96 corner was picked from five candidates (12 / 16 / 20 / 24 / 28) by the owner;
   72 was then added as its own size, at the same shape, 2026-09-28. */
.re-avatar-square.re-avatar-xl{border-radius:min(calc(var(--radius-md) * 1.5),25%)}
.re-avatar-square.re-avatar-2xl{border-radius:min(calc(var(--radius-md) * 2),25%)}
/* A square is a SHAPE, and a pixel radius is not one: 12px is a squircle on a 28px box and a
   circle on a 20px one, because a corner past half the side is clamped to it. So re-avatar-xs
   square was ALWAYS round (roundness 1.00), and an avatar fitted into a compact re-btn-avatar
   (19.6px) turned round too. The third term keeps the corner at 43% of the side, which is
   12.04px at 28 — so every size from 28 up draws exactly what it drew before (0.86 at 28, 0.67 at
   36), and the small ones stop being circles (xs 1.00 → 0.86). It still follows data-radius
   DOWNWARD through --radius-md and still never passes 0.75rem, for the reason below.
   The earlier note: a square avatar at the knob's `full` step (24px) would be clamped by the
   browser into a CIRCLE — the exact shape this class exists to avoid. */
.re-avatar-group{display:inline-flex}
/* Overlap: a background-colored ring at each join, first avatar on top (descending z-index
   1..8 covers a typical group; the "+N" tail sits underneath). */
.re-avatar-group .re-avatar{border:2px solid var(--background);margin-left:-8px;position:relative}
.re-avatar-group .re-avatar:first-child{margin-left:0}
.re-avatar-group .re-avatar:nth-child(1){z-index:8}
.re-avatar-group .re-avatar:nth-child(2){z-index:7}
.re-avatar-group .re-avatar:nth-child(3){z-index:6}
.re-avatar-group .re-avatar:nth-child(4){z-index:5}
.re-avatar-group .re-avatar:nth-child(5){z-index:4}
.re-avatar-group .re-avatar:nth-child(6){z-index:3}
.re-avatar-group .re-avatar:nth-child(7){z-index:2}
.re-avatar-group .re-avatar:nth-child(n+8){z-index:1}

/* ── Divider ── */
.re-divider{border:none;border-top:1px solid var(--border);margin:var(--re-space-4) 0}
.re-divider-vertical{border:none;border-left:1px solid var(--border);align-self:stretch;width:0;margin:0 var(--re-space-3)}
.re-divider-label{display:flex;align-items:center;gap:var(--re-space-3);color:var(--muted-foreground);font-size:calc(0.75rem * var(--font-scale));margin:var(--re-space-4) 0;border:none}
.re-divider-label::before,.re-divider-label::after{content:"";flex:1;border-top:1px solid var(--border)}

/* Components used as links (<a>) must not inherit the UA underline. Keep this list
   complete and in one place — adding it per project means new components get missed
   (e.g. a.re-fab). Includes components that ship in later phases. */
a.re-btn,a.re-chip,a.re-badge,a.re-tag,a.re-card,a.re-fab,a.re-row,a.re-tile,a.re-person,a.re-page-tab,a.re-tab,a.re-navbar-item,a.re-tabbar-item{text-decoration:none}  /* v2 §2.12 — route-bearing tabs are <a> (middle-click / open-in-new-tab / SR "link"); kill the UA underline */

/* ── Tooltip — CSS-only, data-tooltip attribute ── */
.re-tooltip{position:relative;display:inline-flex}
.re-tooltip::after{content:attr(data-tooltip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:var(--foreground);color:var(--background);font-size:calc(0.6875rem * var(--font-scale));font-weight:500;padding:4px 8px;border-radius:var(--radius-inset);white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:300}
.re-tooltip:hover::after,.re-tooltip:focus-visible::after{opacity:1}
/* A hidden balloon takes no room: invisible, it still counted in its scroller's overflow and widened a scrolling box
   sideways (calendar's year at 390: scrollWidth 389 / clientWidth 336, only from the right column's balloons). Collapsed
   until shown; the opacity fade stays. */
.re-tooltip:not(:hover):not(:focus-visible)::after{max-width:0;max-height:0;padding:0;overflow:hidden}
/* v11 §V11-2 — the two triggers above are ALL of them, and neither exists on a touch device:
   (hover: none) means no hover, and tapping does not produce :focus-visible either (measured:
   even a programmatic .focus() leaves :focus-visible false — it is a keyboard-navigation
   signal). So a tooltip is decoration on touch, never the only place a meaning lives. An
   icon-only control whose label is in a tooltip is UNLABELLED for a sighted touch user;
   aria-label solves the screen reader and not them. See the Tooltip page for what to do
   instead. */
.re-tooltip-bottom::after{bottom:auto;top:calc(100% + 6px)}
.re-tooltip-right::after{bottom:auto;top:50%;left:calc(100% + 6px);transform:translateY(-50%)}
.re-tooltip-left::after{bottom:auto;top:50%;left:auto;right:calc(100% + 6px);transform:translateY(-50%)}

/* ── Spinner ── */
@keyframes re-spin{to{transform:rotate(360deg)}}
.re-spinner{display:inline-block;width:18px;height:18px;border:2px solid color-mix(in oklch,var(--foreground) 15%,transparent);border-top-color:var(--primary);border-radius:50%;animation:re-spin .6s linear infinite;flex-shrink:0}
.re-spinner-sm{width:14px;height:14px}
.re-spinner-lg{width:28px;height:28px;border-width:3px}

/* ── Dropdown menu ── */
/* inline-flex, not inline-block. An inline-block builds a LINE BOX for its content, and the
   trigger sits on that line's baseline. With a text or glyph trigger the baseline is where the
   label sits and nothing shows. With a PHOTO avatar the button's baseline becomes the image's
   bottom edge and the line reserves its descender strut below it — measured, a 34.4px trigger
   in a 39.4px dropdown: the trigger 2.49px above the navbar badge beside it, and the menu
   (top: 100% + 6px of the STRETCHED box) opening 11px below the trigger instead of 6. Setting
   the img to display:block does nothing, which is the proof that the image is not the cause —
   the line box is. inline-flex has no line box; measured on four triggers (letter, photo, glyph,
   text), all 34.4 tall, all 0.01 from the badge, all menus at 6.0, and a text trigger inside a
   sentence keeps its baseline (0 → 0). The menu is absolutely positioned with explicit offsets,
   so the flex container does not place it. (redev-system, 2026-09-25) */
.re-dropdown{position:relative;display:inline-flex}
.re-dropdown-menu{position:absolute;top:calc(100% + 6px);left:0;min-width:180px;background:var(--popover);border:1px solid var(--border-elevated);border-radius:var(--radius-md);box-shadow:var(--shadow-md);z-index:300;padding:var(--re-space-1) 0;display:none}
.re-dropdown-menu.open{display:block}
.re-dropdown-menu-right{left:auto;right:0}
.re-dropdown-item{display:flex;align-items:center;gap:var(--re-space-2);padding:calc(var(--re-space-1) + 1px) var(--re-space-3);font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground);text-decoration:none;cursor:pointer;width:100%;background:none;border:none;font-family:inherit;text-align:left;white-space:nowrap}
.re-dropdown-item:hover{background:var(--muted)}
.re-dropdown-item.re-c-danger{color:var(--danger)}
.re-dropdown-item svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}   /* icons were unsized here (re-navbar-menu-item already did this), so an inline SVG rendered at its natural size */
.re-dropdown-desc{color:var(--muted-foreground)}   /* trailing hint on a menu item ("Add photos & files · Upload from computer") */
.re-dropdown-sep{height:1px;background:var(--border);margin:var(--re-space-1) 0}

/* ── Pagination ── */
.re-pagination{display:inline-flex;align-items:center;gap:6px}  /* v3.1 — 2px was too tight between the round pages */
.re-page{display:inline-flex;align-items:center;justify-content:center;min-width:var(--control-h-sm);height:var(--control-h-sm);padding:0 var(--re-space-2);border-radius:var(--radius-full);border:1px solid var(--border-surface);background:none;color:var(--foreground);font-size:calc(0.8125rem * var(--font-scale));font-family:inherit;cursor:pointer;text-decoration:none;transition:background .12s,border-color .12s,color .12s}
.re-page:hover{background:color-mix(in oklch,var(--foreground) 5%,transparent);border-color:var(--primary)}
.re-page.active{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary)}
.re-page:disabled{opacity:.5;cursor:not-allowed;border-color:transparent;background:none}
.re-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:var(--control-h-sm);height:var(--control-h-sm);color:var(--muted-foreground);pointer-events:none}  /* v3.1 — center the dots (was left-aligned in a 20px box) and match the page height */

/* ── Modal / Dialog ── */
/* MARKUP CONTRACT (v2 §D): `.open` goes on the .re-modal-backdrop ONLY; the .re-modal panel
   is its child (centered by the backdrop's flex). Single-class contract — unlike re-sheet. */
.re-modal-backdrop{position:fixed;inset:0;background:color-mix(in oklch,var(--foreground) 30%,transparent);z-index:400;display:none;align-items:center;justify-content:center;padding:20px}
.re-modal-backdrop.open{display:flex}
.re-modal{width:min(480px,94vw);max-height:88vh;overflow-y:auto;background:var(--card);border:1px solid var(--border-elevated);border-radius:var(--radius-surface);box-shadow:var(--shadow-lg);display:flex;flex-direction:column}
.re-modal-sm{width:min(380px,94vw)}
.re-modal-lg{width:min(640px,94vw)}
.re-modal-header{display:flex;align-items:center;justify-content:space-between;gap:var(--re-space-3);min-height:calc(72px * var(--density-mult,1));padding:0 var(--pad-x);border-bottom:1px solid var(--border)}
.re-modal-title{font-size:calc(1.2rem * var(--font-scale));font-weight:600;color:var(--foreground);margin:0}
.re-modal-close{width:30px;height:30px;border-radius:var(--radius-inset);border:none;background:transparent;cursor:pointer;color:var(--muted-foreground);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.re-modal-close:hover{color:var(--foreground);background:color-mix(in oklch,var(--foreground) 7%,transparent)}
.re-modal-body{padding:var(--pad-x);font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground);line-height:1.6}
.re-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--re-space-2);min-height:calc(72px * var(--density-mult,1));padding:0 var(--pad-x);border-top:1px solid var(--border)}

/* ── Toast / Notification ── */
.re-toast-region{position:fixed;bottom:20px;right:20px;z-index:500;display:flex;flex-direction:column;gap:var(--re-space-2);max-width:360px}
.re-toast{display:flex;align-items:flex-start;gap:var(--re-space-2);min-width:260px;padding:var(--pad-y) var(--pad-x);background:var(--popover);border:1px solid var(--border-elevated);border-left:3px solid var(--_solid,var(--primary));border-radius:var(--radius-md);box-shadow:var(--shadow-md);font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground)}
.re-toast-icon{color:var(--_solid,var(--primary));flex-shrink:0;margin-top:1px}
.re-toast-title{font-weight:600}
.re-toast-action{background:none;border:0;padding:0;font:inherit;cursor:pointer;display:inline-block;margin-top:var(--re-space-1);font-weight:600;color:var(--_text,var(--primary));text-decoration:underline;text-underline-offset:2px}
.re-toast-close{margin-left:auto;background:none;border:none;color:var(--muted-foreground);cursor:pointer;padding:0;line-height:1;flex-shrink:0}
.re-toast-close:hover{color:var(--foreground)}

/* ── Progress — bar + ring ── */
.re-progress{width:100%;height:8px;background:color-mix(in oklch,var(--foreground) 10%,transparent);border-radius:999px;overflow:hidden}
.re-progress-bar{height:100%;background:var(--_solid,var(--primary));border-radius:999px;transition:width .3s}
.re-progress-sm{height:5px}
.re-progress-lg{height:12px}
/* v3 V1 — label + readable value on one line above the bar (the continuous counterpart of
   re-progress-steps-compact). The count keeps its own mono/tabular/muted look; the row sets
   the label text. Put a re-progress bar right after it. */
.re-progress-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 6px;font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground)}
.re-progress-ring{transform:rotate(-90deg)}
.re-progress-ring circle{fill:none}
.re-progress-ring .re-ring-track{stroke:color-mix(in oklch,var(--foreground) 10%,transparent)}
.re-progress-ring .re-ring-bar{stroke:var(--_solid,var(--primary));stroke-linecap:round;transition:stroke-dashoffset .3s}


/* ════════════════════════════════════════════════════════════
   Orphan componentler (Faz 3 Astro rebuild'de re-* prefix'lenecek)
   ════════════════════════════════════════════════════════════ */

/* ── Card ───────────────────────────────────────────────────── */
/* --border-surface, not --border: a box that holds content and sits in the flow is the
   RESTING tier (1.58:1), between the separator (--border, 1.35) and something that floats
   over content with a shadow (--border-elevated, 1.75). Controls keep --border and floating
   surfaces keep --border-elevated — three roles, the same three the radius scale already
   names (inset / control / surface). (labs §V20-1) */
/* position:relative so an re-glow / re-dots can sit in it — a card is the glow's most natural
   box and two consumers had to write this line locally, in both of them. Offsetless relative is
   visually inert, so there is no case for making it conditional. (labs v35 §2) */
.re-card{position:relative;overflow:hidden;
  --_cpad-y:var(--pad-y);--_cpad-x:var(--re-box-inset);--_crad:var(--radius-surface);
  background:var(--card);border:1px solid var(--border-surface);
  border-radius:var(--_crad);box-shadow:var(--shadow-sm)}
.re-card-flat{box-shadow:none}   /* v6 V6-1 — opt OUT of the content-surface elevation: an inset panel, a box inside another card, a print/export view. Makes "flat" an intention instead of a leftover. */  /* v3 V3 — --radius-surface (18px at the default step) matches re-stat / re-row-list so content surfaces share one radius when stacked */
/* Interactive card — hover lift + shadow + brand border (product/link cards) */
.re-card-interactive{transition:box-shadow .15s,border-color .15s,transform .15s;cursor:pointer}
.re-card-interactive:hover{box-shadow:var(--shadow-md);border-color:color-mix(in oklch,var(--brand) 30%,transparent);transform:translateY(-2px)}
/* Tap card — app lists: border on hover + a press (:active) scale, no hover lift
   (touch has no hover, and a lift feels wrong in a scrolling list). */
.re-card-tap{transition:border-color .12s,transform .1s;cursor:pointer}
.re-card-tap:hover{border-color:color-mix(in oklch,var(--brand) 30%,transparent)}
.re-card-tap:active{transform:scale(.985)}

/* Section header — eyebrow + title + subtitle block (centered variant) */
.re-section-header{display:flex;flex-direction:column;gap:calc(8px * var(--density-mult,1))}
.re-section-header-center{align-items:center;text-align:center}
.re-section-eyebrow{font-size:calc(0.75rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--muted-foreground)}
.re-section-title{margin:0;font-size:calc(var(--font-scale) * clamp(1.4rem, 3.5vw, 2rem));font-weight:700;line-height:1.2;color:var(--foreground)}
.re-section-sub{margin:0;font-size:calc(0.9375rem * var(--font-scale));line-height:1.6;color:var(--text-strong-muted);max-width:60ch}
.re-section-header-center .re-section-sub{margin-left:auto;margin-right:auto}
.re-card-header{padding:var(--_cpad-y) var(--_cpad-x);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:var(--re-space-3)}
.re-card-body{padding:var(--_cpad-y) var(--_cpad-x)}
/* The header can carry the role on its own — the same three words the card uses, one level
   down. A coloured band over a plain body is a real shape (a brief, a summary, an alert-like
   panel) and it was only reachable by colouring the whole card, which is a different thing. */
.re-card-header-soft{background:var(--_tint,color-mix(in oklch,var(--primary) 8%,transparent))}
.re-card-header-solid{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));
  border-bottom-color:transparent}
.re-card-header-solid :is(.re-text-muted,.re-card-sub){color:color-mix(in oklch,var(--_fg,white) 72%,transparent)}
.re-card-footer{padding:calc(var(--_cpad-y) * .7) var(--_cpad-x);border-top:1px solid var(--border);display:flex;align-items:center;justify-content:flex-end;gap:var(--re-space-2)}

/* ══ Card options — style · size · hover · media · badge ═══════════════════════════════
   A card is the content box INSIDE a section, the way a button is the control inside a row,
   and it needs the same three knobs a button has — colour, size, variant — from the same
   dictionary. Until now it had none of them: colour came from five hardcoded class names
   (re-card-success and friends), there was no size, and "outline" did not exist.

   The dictionary is the DS's, not a new one:
     colour   re-c-{role}            the canonical modifier, same as every other component
     size     re-card-sm / -lg       the canonical steps, and they retune PADDING and RADIUS
     variant  re-card-outline / -soft / -solid   the same three words re-btn uses

   So `re-card re-c-success` is what `re-card-success` was, and `re-btn re-c-success` is its
   sibling — one sentence for both. The old names still work; see the note under them. */

/* Size. A card's size is its PADDING and its corner, not a height — a content box is as tall
   as what is in it. --pad-y/--pad-x are the density primitives, so these steps compose with
   data-density instead of fighting it. */
.re-card-sm{--_cpad-y:calc(var(--pad-y) * .7);--_cpad-x:calc(var(--re-box-inset) * .7);
  --_crad:var(--radius-inset)}
.re-card-lg{--_cpad-y:calc(var(--pad-y) * 1.35);--_cpad-x:calc(var(--re-box-inset) * 1.25)}

/* Variant. Colour comes from re-c-{role}; these three say how much of it to use.
   Default (no variant) is the resting surface: --card ground, --border-surface edge.
   ⚠ --_tint / --_solid / --_edge / --_fg are the colour dictionary's own tokens, so a card
   picks up a role exactly the way a button, a badge and an alert do. Without re-c-* they fall
   back to the neutral surface and every variant still renders. */
.re-card-soft{background:var(--_tint,color-mix(in oklch,var(--primary) 8%,transparent));
  border-color:var(--_edge,var(--border-surface))}
.re-card-outline{background:transparent;border-color:var(--_edge,var(--border-surface));
  box-shadow:none}
.re-card-solid{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));
  border-color:transparent;
  /* REMAP the tokens, do not chase the classes. A solid card's ground is the role colour, so
     every tier that was tuned for the PAGE ground is wrong inside it — and the first version
     of this rule listed two class names, which caught re-text-muted and missed everything
     else, including an inline `color:var(--muted-foreground)`. Redeclaring the tokens fixes
     whatever reads them, which is the point of having tokens.
     --border too: a divider tuned for the page is invisible on a filled card. */
  /* 86%, scanned not chosen: on the primary fill 72% measured 3.83, 82% 4.47 and 86% 4.75.
     The muted tier is quieter than the label and still has to clear AA — a "quiet" colour that
     cannot be read is not a tier, it is a mistake with a name. */
  --muted-foreground:color-mix(in oklch,var(--_fg,white) 86%,transparent);
  --foreground:var(--_fg,var(--primary-foreground));
  --border:color-mix(in oklch,var(--_fg,white) 22%,transparent)}

/* Hover. re-card-interactive lifts (a tile you click through to) and re-card-tap presses
   (a row in a list). This is the quiet third: the card reacts but does not move — for a card
   that CONTAINS controls, where a lift under the pointer makes the buttons feel unstable. */
.re-card-hover{transition:border-color .15s,box-shadow .15s}
.re-card-hover:hover{border-color:var(--_edge,color-mix(in oklch,var(--brand) 30%,transparent));
  box-shadow:var(--shadow-md)}

/* Media — an image band that reaches the card's edges. The card clips it, so the corner is
   the card's, and the radius follows --_crad without the image having to know it. */
.re-card-media{display:block;width:100%;margin:0}
.re-card-media > :is(img,video,svg){display:block;width:100%;height:auto;object-fit:cover}
.re-card-media-h{height:var(--re-card-media-h,180px)}
.re-card-media-h > :is(img,video){height:100%}

/* Badge — a marker pinned to the card's corner, over the media if there is any. Not a
   re-badge variant: the badge stays itself, this is only the position. */
.re-card-badge{position:absolute;top:var(--re-space-2);right:var(--re-space-2);z-index:2}
.re-card-badge-start{right:auto;left:var(--re-space-2)}

/* Header / body / footer read the size tokens, so one class moves all three. */

/* ══ re-edge — one thick coloured side, on any box ═════════════════════════════════════
   This shape was already in the file three times, each hardcoded to one side and one set of
   role names: re-alert's 3px left border, re-card-top-accent's 3px top bar with five
   re-card-top-{role} classes, and re-cal-event's left rail. It is one idea — "mark this box's
   edge with a role colour" — so it is one modifier with the standard dictionary:

     .re-card .re-edge .re-edge-start .re-c-danger

   Position: -top (default) · -bottom · -start · -end. Logical, not left/right, because the
   inline sides flip in RTL and a "leading edge" marker has to flip with them.
   Thickness: --re-edge-w, with -sm (2px) and -lg (5px) as the named steps.

   Drawn as a ::before, not a border, and that is the whole reason it composes: a border on one
   side shifts the box's content by its width on that side only, so the text stops being
   centred — which is a cost re-alert accepts because it is always left-marked, and a cost a
   general modifier cannot. It also means the marker follows the box's own radius when the box
   clips (re-card does).
   ⚠ It owns ::before. On a box that already uses one (re-glow's container does not — the glow
   is an element), reach for the element form instead. */
.re-edge{position:relative;--re-edge-w:3px}
.re-edge-sm{--re-edge-w:2px}
.re-edge-lg{--re-edge-w:5px}
.re-edge::before{content:"";position:absolute;z-index:1;pointer-events:none;
  background:var(--_solid,var(--primary));
  inset:0 0 auto 0;height:var(--re-edge-w)}
/* re-edge-top exists even though top is the default, and that is not redundancy: with
   -bottom, -start and -end named, an unnamed top makes the reader stop and check whether
   they missed a class. A consumer asked. Writing it is also the difference between "top
   because I chose it" and "top because I forgot" — the same distinction the indigo preset
   was added for. */
.re-edge-top::before{inset:0 0 auto 0;height:var(--re-edge-w);width:auto}
.re-edge-bottom::before{inset:auto 0 0 0;height:var(--re-edge-w)}
.re-edge-start::before{inset:0 auto 0 0;width:var(--re-edge-w);height:auto}
.re-edge-end::before{inset:0 0 0 auto;width:var(--re-edge-w);height:auto}
/* RTL: the inline sides swap. Written out rather than using inset-inline, because `inset` with
   `auto` on one side is what positions the bar and the logical shorthand does not take that
   shape cleanly. */
[dir="rtl"] .re-edge-start::before{inset:0 0 0 auto;width:var(--re-edge-w);height:auto}
[dir="rtl"] .re-edge-end::before{inset:0 auto 0 0;width:var(--re-edge-w);height:auto}

/* Semantic Card Variants — SUPERSEDED, kept working.
   `re-card-success` is `re-card re-c-success re-card-soft`, and `re-card-top-{role}` is
   `re-edge re-c-{role}`. Both of those read from the DS's own dictionary instead of adding a
   class name per role, which is the difference: five hardcoded names could not answer
   "what about warning on the left edge", and the modifier can.
   Left in place because consumers use them; measured at 0 breaking changes. */
.re-card-success{border-color:var(--success);background:color-mix(in oklch,var(--success) 8%,transparent)}
.re-card-warning{border-color:var(--warning);background:color-mix(in oklch,var(--warning) 8%,transparent)}
.re-card-destructive{border-color:var(--destructive);background:color-mix(in oklch,var(--destructive) 8%,transparent)}
.re-card-info{border-color:var(--info);background:color-mix(in oklch,var(--info) 8%,transparent)}
.re-card-accent{border-color:var(--accent);background:color-mix(in oklch,var(--accent) 8%,transparent)}
.re-card-top-accent{position:relative;overflow:hidden}
.re-card-top-accent::before{content:'';position:absolute;top:0;left:0;right:0;height:3px}
.re-card-top-primary::before{background:var(--primary)}
.re-card-top-success::before{background:var(--success)}
.re-card-top-warning::before{background:var(--warning)}
.re-card-top-info::before{background:var(--info)}


/* ── Table ──────────────────────────────────────────────────── */
/* Single canonical table — density-aware padding + hover + sizes.
   At comfortable density this matches the previous fixed-px table (~15/10px). */
.re-table{width:100%;border-collapse:collapse;font-size:calc(0.8125rem * var(--font-scale))}
.re-table th{text-align:left;vertical-align:middle;padding:var(--re-space-2) var(--re-space-3);background:var(--surface);font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted-foreground);border-bottom:1px solid var(--border)}
.re-table td{padding:var(--re-space-2) var(--re-space-3);vertical-align:middle;border-bottom:1px solid color-mix(in oklch,var(--border) 60%,transparent);color:var(--foreground)}
.re-table tr:last-child td{border-bottom:none}
.re-table tr:hover td{background:color-mix(in oklch,var(--foreground) 3%,transparent)}
.re-table-sm th{padding:var(--re-space-1) var(--re-space-2)}.re-table-sm td{padding:var(--re-space-1) var(--re-space-2)}
.re-table-lg th{padding:var(--re-space-4) var(--re-space-3)}.re-table-lg td{padding:var(--re-space-3)}
/* Zebra rows */
.re-table-zebra tbody tr:nth-child(even) td{background:color-mix(in oklch,var(--foreground) 2.5%,transparent)}
/* Sticky header — wrap the table in a scroll container (max-height) */
.re-table-sticky thead th{position:sticky;top:0;z-index:1}
/* Right-aligned numeric column (th + td) */
.re-table-num{text-align:right;font-variant-numeric:tabular-nums}
/* Sortable header */
.re-table th.re-table-sort{cursor:pointer;user-select:none;white-space:nowrap;transition:color .1s}
.re-table th.re-table-sort:hover{color:var(--foreground)}
.re-table-sort-icon{display:inline-block;width:10px;margin-left:5px;opacity:.45}
.re-table th.re-table-sort.asc .re-table-sort-icon,.re-table th.re-table-sort.desc .re-table-sort-icon{opacity:1;color:var(--primary)}
.re-table-sort-icon::after{content:'↕'}
.re-table th.re-table-sort.asc .re-table-sort-icon::after{content:'↑'}
.re-table th.re-table-sort.desc .re-table-sort-icon::after{content:'↓'}
/* Selectable rows (checkbox column) */
.re-table tr.re-row-sel td{background:color-mix(in oklch,var(--primary) 8%,transparent)}
.re-table td.re-table-check,.re-table th.re-table-check{width:1%;padding-right:0}
/* Bordered data-table shell — rounded frame around a full-width table, with an
   optional filter header and a pagination footer (one cohesive unit). */
.re-table-shell{border:1px solid var(--border-surface);border-radius:var(--radius);overflow:hidden;background:var(--card)}
.re-table-shell > .re-table{width:100%}
.re-table-shell-head{padding:var(--re-space-2) var(--re-space-3);border-bottom:1px solid var(--border)}
.re-table-foot{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--re-space-2);padding:var(--re-space-2) var(--re-space-3);border-top:1px solid var(--border)}
.re-table-foot-info{font-size:calc(0.75rem * var(--font-scale));color:var(--muted-foreground)}


/* ── Tab bar (re-tab-bar / re-tab) ───────────────────────────────────────
   An underline-indicator tab strip. Two shapes:
     · flush (default)   — text on a line; the spacing between tabs is the bar's gap
     · re-tab-bar-pad    — each tab is a padded box; the padding IS the spacing (gap 0)
   Orientation  -bottom (bar sits under the panel) · -left / -right (vertical rail).
                The indicator always sits on the bar's own border edge.
   Size         -sm / (default) / -lg. The type scale is shared, the box is not:
                a flush tab is text on a line and a padded tab is a box, so each
                keeps its own vertical rhythm rather than one --pad-y for both.
   Alignment    the flex utilities — re-justify-center / re-justify-end — plus
                -justify for equal-width cells, which no utility can express
                because it applies to the CHILDREN, not the bar.
   Overflow     -scroll (horizontal swipe strip), or move the tail into a
                dropdown; see the docs for when each is right.
   Active       .active OR [aria-selected="true"], so a consumer that wires the
                ARIA properly does not also have to toggle a class.
   Spacing note: the gap lives on the BAR (it used to be margin-right on the tab).
   A stray re-tab outside an re-tab-bar therefore has no trailing space — and a
   trailing margin is also what made right/centre alignment sit 1.25rem off. */
.re-tab-bar{display:flex;gap:var(--re-tab-gap);border-bottom:1px solid var(--border);
  --re-tab-fs:0.8125rem;--re-tab-gap:1.25rem;
  --re-tab-pad-y:calc(7px * var(--density-mult,1));--re-tab-pad-x:0}
.re-tab{padding:var(--re-tab-pad-y) var(--re-tab-pad-x);font-size:calc(var(--re-tab-fs) * var(--font-scale));font-weight:500;color:var(--muted-foreground);cursor:pointer;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;background:none;font-family:inherit;display:inline-flex;align-items:center;justify-content:center;gap:6px;white-space:nowrap}
.re-tab:hover{color:var(--foreground)}
.re-tab.active,.re-tab[aria-selected="true"],.re-tab[aria-current="page"]{color:var(--primary);border-bottom-color:var(--primary)}

/* size — flush keeps the tighter rhythm, the padded box gets a real one */
.re-tab-bar-sm{--re-tab-fs:0.75rem;--re-tab-icon:13px;--re-tab-pad-y:calc(5px * var(--density-mult,1))}
.re-tab-bar-lg{--re-tab-fs:0.875rem;--re-tab-icon:17px;--re-tab-pad-y:calc(10px * var(--density-mult,1))}
.re-tab-bar-pad{--re-tab-gap:0;--re-tab-pad-y:calc(10px * var(--density-mult,1));--re-tab-pad-x:calc(15px * var(--density-mult,1))}
.re-tab-bar-pad.re-tab-bar-sm{--re-tab-pad-y:calc(7px * var(--density-mult,1));--re-tab-pad-x:calc(10px * var(--density-mult,1))}
.re-tab-bar-pad.re-tab-bar-lg{--re-tab-pad-y:calc(15px * var(--density-mult,1));--re-tab-pad-x:calc(25px * var(--density-mult,1))}

/* equal-width cells (UIkit's uk-child-width-expand case) */
.re-tab-bar-justify > .re-tab{flex:1 1 0;min-width:0}

/* -bottom — the bar sits BELOW its panel, so both the rule and the indicator flip */
.re-tab-bar-bottom{border-bottom:0;border-top:1px solid var(--border)}
.re-tab-bar-bottom > .re-tab{border-bottom:0;margin-bottom:0;border-top:2px solid transparent;margin-top:-1px}
.re-tab-bar-bottom > .re-tab.active,.re-tab-bar-bottom > .re-tab[aria-selected="true"],.re-tab-bar-bottom > .re-tab[aria-current="page"]{border-top-color:var(--primary)}

/* -left / -right — a vertical rail. Horizontal padding is not optional here: the
   indicator sits on the shared edge, so a 0-padding tab would put the label
   against it. Pair with aria-orientation="vertical" (the arrow keys change axis). */
.re-tab-bar-left,.re-tab-bar-right{flex-direction:column;align-items:stretch;border-bottom:0;
  --re-tab-gap:0;--re-tab-pad-x:calc(15px * var(--density-mult,1))}
.re-tab-bar-left{border-right:1px solid var(--border)}
.re-tab-bar-right{border-left:1px solid var(--border)}
:is(.re-tab-bar-left,.re-tab-bar-right).re-tab-bar-sm{--re-tab-pad-x:calc(10px * var(--density-mult,1))}
:is(.re-tab-bar-left,.re-tab-bar-right).re-tab-bar-lg{--re-tab-pad-x:calc(25px * var(--density-mult,1))}
:is(.re-tab-bar-left,.re-tab-bar-right) > .re-tab{justify-content:flex-start;text-align:left;border-bottom:0;margin-bottom:0}
.re-tab-bar-left > .re-tab{border-right:2px solid transparent;margin-right:-1px}
.re-tab-bar-right > .re-tab{border-left:2px solid transparent;margin-left:-1px}
/* The padding the comment above calls not optional is the LINE side's: it keeps the label off the
   indicator. The OUTER side had the same 15px for no reason, and it is the side a vertical menu
   has to line up with — in a profile column the avatar and the name start at the column's edge,
   and the labels started 16.8px in (10.8 at compact). The outer side is now 0 and the label sits
   on the edge; the line side keeps --re-tab-pad-x as the gap to the indicator, exactly the gap
   -right already had between its line and its labels. Only redev-id uses a vertical bar in the
   family, and it asked for this. */
.re-tab-bar-left > .re-tab{padding-left:0}
.re-tab-bar-right > .re-tab{padding-right:0}
/* re-tab-bar-end — the labels go to the other side. In -left that is TOWARD the line, so the
   line-side gap is what separates them from it; in -right it is the outer edge, flush. */
:is(.re-tab-bar-left,.re-tab-bar-right).re-tab-bar-end > .re-tab{justify-content:flex-end;text-align:right}
/* re-tab-bar-auto — a vertical bar as wide as its longest label, instead of the column. The
   default (fill) makes the line a divider at the column's edge; auto puts it beside the labels,
   18.8px from the longest one. The consumer picks. */
:is(.re-tab-bar-left,.re-tab-bar-right).re-tab-bar-auto{align-self:flex-start;width:max-content;max-width:100%}
/* An icon in a vertical tab sits flush at the label's start with no margin, in a slot of ONE
   width, so every label in the column starts at one x whatever the icon's drawing. Nothing sized
   a tab's svg before: it drew at whatever width attribute the call site passed. When some tabs in
   the bar have an icon and some do not, the icon-less ones reserve the same slot — the
   re-section-label rule, for the same reason. The slot is read on the TAB (1em x ratio), because
   --re-icon is declared on the svg and an ancestor reading it gets nothing. */
:is(.re-tab-bar-left,.re-tab-bar-right) > .re-tab > svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0;margin:0}
.re-tab-bar-left:has(> .re-tab > svg) > .re-tab:not(:has(> svg)){padding-left:calc(1em * var(--re-icon-ratio) + 6px)}
.re-tab-bar-right:has(> .re-tab > svg) > .re-tab:not(:has(> svg)){padding-left:calc(var(--re-tab-pad-x) + 1em * var(--re-icon-ratio) + 6px)}
.re-tab-bar-left > .re-tab.active,.re-tab-bar-left > .re-tab[aria-selected="true"],.re-tab-bar-left > .re-tab[aria-current="page"]{border-right-color:var(--primary)}
.re-tab-bar-right > .re-tab.active,.re-tab-bar-right > .re-tab[aria-selected="true"],.re-tab-bar-right > .re-tab[aria-current="page"]{border-left-color:var(--primary)}

/* -scroll — a swipe strip when the set outgrows the width. Two things here are
   not decoration:
   (1) overflow-y:hidden — per spec, setting one axis to auto promotes the
       other's `visible` to `auto`, so overflow-x alone makes the strip
       vertically draggable too (measured on re-page-tabs-scroll: 38 vs 34).
       `clip` would be the stricter value but is NOT available here: when the
       other axis scrolls, `clip` computes back to `hidden` (measured — the
       declaration changes nothing). What `hidden` leaves behind is 2px of
       script-only scrollTop on a coarse pointer, where the 44px ::after hit
       area outgrows the 38px box. Measured harmless: scrollIntoView (default,
       nearest, center) and focus() all leave scrollTop at 0 — only an explicit
       `el.scrollTop = n` moves it, which nothing writes on a tab strip.
   (2) the rule moves from border-bottom to an INSET shadow. A flush tab hangs
       1px past the bar's content box so its indicator paints over the rule —
       but a border lives outside the clip region, so with overflow the
       indicator would be cut and the grey rule would stay visible under it.
       An inset shadow is drawn inside the padding box, where the tab can
       still cover it, so the scrolling bar looks identical to the static one. */
.re-tab-bar-scroll{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scroll-padding-inline:var(--re-tab-pad-x);scrollbar-width:none;-ms-overflow-style:none}
.re-tab-bar-scroll:not(.re-tab-bar-bottom){border-bottom:0;box-shadow:inset 0 -1px 0 var(--border)}
.re-tab-bar-scroll:not(.re-tab-bar-bottom) > .re-tab{margin-bottom:0}
.re-tab-bar-scroll::-webkit-scrollbar{height:0;display:none}
.re-tab-bar-scroll > .re-tab{flex-shrink:0}

/* ── Segmented control (re-segment) — enclosed pill toggle, one active ──
   A track of options where the active one is a raised chip. Use for view
   switchers (Preview/Markup, list/grid), ranges, density, etc. JS toggles
   .active on an .re-segment-item. Density-aware; re-c-{role} tints the active
   text; -sm/-lg resize. */
/* Equal cells by default (inline-grid + 1fr → every cell the width of the widest),
   like iOS/Android/shadcn segmented controls, so the active pill is centred. Add
   re-segment-fill to stretch the whole control to its container's width. */
.re-segment{display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;padding:3px;background:var(--muted);border-radius:var(--radius-md)}
.re-segment-fill{display:grid;width:100%}
/* Same token set as re-btn, for the same reason: the crowded re-ph-actions slot has to size
   what it holds, and the alternative is re-declaring -sm's three numbers in a second place.
   ⚠ The segment's -sm is NOT the button's -sm — 0.6875rem and pad-x*.55 against the button's
   0.75rem and *.72 — so it gets its own set rather than borrowing --re-ctl-sm-*. Found the
   hard way: a rule pointing the segment at the button's step was written here and did nothing,
   because the real -sm rule is later in the file at equal specificity. */
.re-segment-item{--_h:calc(var(--control-h-md) - 6px);--_px:calc(var(--pad-x) * .75);--_fs:calc(0.75rem * var(--font-scale));display:flex;align-items:center;justify-content:center;gap:6px;height:var(--_h);padding:0 var(--_px);border:none;background:none;color:var(--muted-foreground);font-family:inherit;font-size:var(--_fs);font-weight:500;border-radius:calc(var(--radius-md) - 3px);cursor:pointer;white-space:nowrap;transition:color .12s,background .12s}
.re-segment-item:hover:not(.active){color:var(--foreground)}
.re-segment-item.active{background:var(--card);color:var(--_text,var(--foreground));box-shadow:var(--shadow-sm)}
:root{--re-seg-sm-h:calc(var(--control-h-sm) - 6px);--re-seg-sm-px:calc(var(--pad-x) * .55);--re-seg-sm-fs:calc(0.6875rem * var(--font-scale))}
.re-segment-sm .re-segment-item{--_h:var(--re-seg-sm-h);--_px:var(--re-seg-sm-px);--_fs:var(--re-seg-sm-fs)}
.re-segment-lg .re-segment-item{--_h:calc(var(--control-h-lg) - 6px);--_px:var(--pad-x);--_fs:calc(0.8125rem * var(--font-scale))}
/* Label slot — "Group by: status / source / tag". A segment is very often the label plus the
   choices, and rebuilding that by hand out of a chip group is how you end up with two
   components that look alike and size differently.
   The control is inline-grid with grid-auto-columns:1fr so every OPTION is the width of the
   widest one; the label must not join that scheme, so :has() gives it an explicit auto first
   column and the options keep the implicit 1fr. Works in all three sizes. */
.re-segment:has(> .re-segment-label){grid-template-columns:auto}
.re-segment-label{display:flex;align-items:center;padding-inline:8px 4px;white-space:nowrap;
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground)}
.re-segment-sm .re-segment-label{padding-inline:6px 3px;font-size:calc(0.625rem * var(--font-scale))}
/* Shape option — a fully rounded track and pills inside it, for chrome that sits next to
   other round things (a calendar's day strip, a filter row of chips). It is an OPTION on the
   real control rather than a look someone rebuilds out of a chip group: composes with all
   three sizes and with the label slot. */
.re-segment-pill{border-radius:var(--radius-full)}
.re-segment-pill .re-segment-item{border-radius:var(--radius-full)}
.re-segment-pill .re-segment-label{padding-inline:11px 5px}
.re-segment-sm.re-segment-pill .re-segment-label{padding-inline:9px 4px}
.re-segment-lg.re-segment-pill .re-segment-label{padding-inline:13px 6px}
.re-segment-lg .re-segment-label{padding-inline:10px 5px;font-size:calc(0.75rem * var(--font-scale))}
/* Shape option — a chip that is FLUSH with the track instead of inset in it.
   The default control keeps the chip 3px inside a padded track; this one gives the track a
   1px ring and lets the chip cover it, so the active option reads as a raised object sitting
   ON the track rather than a tile dropped INTO it (the iOS/Arc segmented look).

   The whole thing is one margin: the chip is `control-h + 2` tall with `margin:-2px`, so its
   border box hangs 1px PAST the track on every side. Flush was the first attempt and it was
   measured wrong in light: with the ring the colour of the raised surface, a chip that only
   meets the track's edge reads as the same object. 1px of overhang is what makes it a chip
   sitting ON the track. Two consequences, both load-bearing:
     · the OUTER LAYOUT height is unchanged — margin box = control-h - 2, plus the 2px ring
       = control-h exactly, so the contract with re-btn at the same size still holds. Only
       the PAINT extends 1px, which is why the box can overhang without moving anything;
     · the 2px gap and the two -2px margins overlap neighbours by 2px, so the chips have no
       seam between them and the active one reads as having displaced the others.
   `border-radius:inherit` rather than a repeat of the value: flush means the chip's corner
   IS the track's corner, so it must follow -pill and the default without a second rule. */
.re-segment-flush{padding:0;border:1px solid var(--card)}
.re-segment-flush > .re-segment-item{height:calc(var(--control-h-md) + 2px);margin:-2px;border-radius:inherit}
.re-segment-sm.re-segment-flush > .re-segment-item{height:calc(var(--control-h-sm) + 2px)}
.re-segment-lg.re-segment-flush > .re-segment-item{height:calc(var(--control-h-lg) + 2px)}
/* the label is not a chip: it stays inside the ring, so it gets the padding back */
.re-segment-flush > .re-segment-label{margin-inline-start:1px}

/* ── Table of contents (re-toc) — "on this page" list with a left guide rail ──
   Title (icon + label) over a border-left rail of links; active link is accent
   (--c-link, re-tints with data-brand). JS scrollspy toggles .active. */
.re-toc-title{display:flex;align-items:center;gap:7px;width:100%;margin:0 0 8px;padding:0;
  background:none;border:0;font-family:inherit;text-align:left;
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-foreground)}
.re-toc-title svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
.re-toc-list{display:flex;flex-direction:column;border-left:1px solid var(--border);margin-left:6px;padding-left:13px}
.re-toc-lnk{display:block;padding:3px 0;font-size:calc(0.75rem * var(--font-scale));line-height:1.4;color:var(--muted-foreground);text-decoration:none;transition:color .12s}
.re-toc-lnk:hover{color:var(--foreground)}
.re-toc-lnk.active{color:var(--c-link)}
.re-toc-lnk.re-toc-sub{padding-left:12px}
/* ── Numbered contents ──────────────────────────────────────────────────────────
   A numbered outline breaks the moment the list reaches 10: "9." and "10." are different
   widths in a proportional face, so every title after the tenth sits a fraction further
   right and the column stops being a column. Consumers work around it by typing "01" into
   the heading, which fixes the width by luck and puts presentation in the content.

   Two things fix it properly, and both are needed. The number is its own GRID COLUMN, so the
   titles align at one x whatever the digit count — that is the alignment. And the digits are
   tabular, so the numbers themselves line up inside that column — that is the rhythm. A
   fixed-width column alone still leaves 1 and 11 ragged against each other.

   The counter comes from the list, so the markup carries no numbers at all: renumbering is
   deleting a row. --re-toc-num-w widens the column past 99. */
.re-toc-numbered{counter-reset:re-toc}
.re-toc-numbered > .re-toc-lnk{display:grid;grid-template-columns:var(--re-toc-num-w,2ch) 1fr;
  column-gap:var(--re-space-2);counter-increment:re-toc}
.re-toc-numbered > .re-toc-lnk::before{content:counter(re-toc);
  font-variant-numeric:tabular-nums;text-align:right;color:var(--muted-foreground);opacity:.7}
.re-toc-numbered > .re-toc-lnk.active::before{color:inherit;opacity:1}
/* Leading zeros — a choice about how the outline reads, not about alignment: the grid column
   above already aligns the titles either way. */
.re-toc-numbered-pad > .re-toc-lnk::before{content:counter(re-toc,decimal-leading-zero)}
/* A sub-item is not a numbered step; it keeps the title column and skips the number. */
.re-toc-numbered > .re-toc-lnk.re-toc-sub{padding-left:0;counter-increment:none}
.re-toc-numbered > .re-toc-lnk.re-toc-sub::before{content:""}

/* ── re-toc / re-toc-layout / re-toc-collapse ─────────────────────────────────
   The TOC had classes for its parts but none for the panel or the column it sits in, so
   every layout hand-rolled both with inline styles — and inline styles cannot answer a
   media query. Measured at 390px: a 152px TOC stayed beside the article, taking a third of
   the screen off a page that was already narrow.

   `re-toc-layout` is the article + TOC row (the shape base.css has been describing in prose
   since it was written). `re-toc-collapse` is the phone behaviour: the row stacks, the TOC
   moves ABOVE the article — a table of contents read after the content is not a table of
   contents — and folds into a single tappable row that opens the list.

   Above the breakpoint the modifier does nothing at all, so it is safe to always write.

   Width and sticky offset are TOKENS (--re-toc-w / --re-toc-top), not inline styles, because
   an inline `width:180px` beats a media query — specificity has no answer to a style
   attribute. Measured: topbar-toc kept its 180px column on a 390px phone for exactly that
   reason while the other two went full width. A token feeds the desktop rule and leaves the
   phone rule free to override. */
.re-toc{width:var(--re-toc-w,152px);flex-shrink:0;position:sticky;top:var(--re-toc-top,20px);align-self:flex-start}
.re-toc-layout{display:flex;gap:var(--re-space-8);align-items:flex-start}
.re-toc-layout > :first-child{flex:1;min-width:0}
/* ── re-aside-layout — a fixed side column BEFORE the content (settings, profile) ────
   The mirror of re-toc-layout, and a separate class rather than a modifier because the order
   is the difference: re-toc-layout's first child is the flexible content and the TOC trails it,
   here the column leads and the content fills. On a narrow layout the column goes ON TOP.

   Named after the column's role, like re-toc — not "rail": in this DS a rail is the icon strip
   (re-sidebar-rail, re-slot-rail), and a second meaning would split the word.

   The threshold is the layout's OWN width, not the viewport's: a settings page usually sits
   beside a sidebar, so a viewport breakpoint would stack it too late on a desktop with the
   sidebar open and too early with it collapsed. The layout is the container and its CHILDREN
   are what the query styles — a container cannot restyle itself, its descendants it can.
   Measured: 966px wide → two columns (column 250, content 668); 666px → one column, the column
   first and no longer sticky, because a sticky header-sized block on top of the content would
   pin itself over what it introduces.

   ⚠ container-type:inline-size means the layout cannot take its width from its content. Put it
   where something gives it a width (a block, a grid cell, a flex item that stretches) — inside
   a shrink-to-fit parent it collapses to zero.

   Width and sticky offset are tokens, as re-toc's are, so a consumer never needs a style
   attribute that would beat the narrow rule. */
.re-aside-layout{container-type:inline-size;display:flex;flex-wrap:wrap;gap:var(--re-space-8);align-items:flex-start}
.re-aside-layout > .re-aside{flex:0 0 var(--re-aside-w,250px);min-width:0;position:sticky;top:var(--re-aside-top,16px)}
.re-aside-layout > :not(.re-aside){flex:1 1 0;min-width:0}
@container (max-width: 860px){
  .re-aside-layout > .re-aside{flex-basis:100%;position:static}
}
/* re-aside-select — the menu as a SELECT once the layout stacks. Stacked, the column is the full
   width and a vertical menu stretched across it put its line and active indicator at the far edge
   — measured at 360px, 222.6px from the longest label — and a long menu became a tall block the
   page had to scroll past. A native select is one line and opens the phone's own picker.
   It is opt-in by PRESENCE: put a `<select class="re-select re-aside-select">` in the column and,
   while stacked, the tab bar hides and the select shows; above the threshold it is the other way
   round. A column without one keeps its menu. display:none takes the hidden one out of the tab
   order and the accessibility tree, so only one control is ever exposed.
   ⚠ The select does not navigate — that is the consumer's routing (one onchange). The DS's JS is
   vendored by a minority of the family, so wiring it here would work in few places. */
.re-aside-select{display:none}
@container (max-width: 860px){
  .re-aside-layout > .re-aside:has(.re-aside-select) .re-tab-bar{display:none}
  .re-aside-layout > .re-aside .re-aside-select{display:block}
}
/* re-aside-identity — the avatar and the name/email block at the head of the column. Two columns:
   stacked, avatar on top, flush left, which is what a 250px column has room for. One column: the
   column is the full width and stacking wastes it, so the avatar goes to the LEFT with the name
   and email beside it, vertically centred — the owner's pick, from the demo where it sat that way.
   The switch follows the same container query as the layout, because it is the same event.
   The avatar keeps its size class. A layout rule that turned re-avatar-xl into something other
   than 72px would make the class name describe a size that is not drawn — the defect PHANTOM SIZE
   exists for, moved from a comment into a selector. */
.re-aside-identity{display:flex;flex-direction:column;align-items:flex-start;gap:var(--re-space-2);min-width:0}
.re-aside-identity > :not(.re-avatar){min-width:0;max-width:100%}
@container (max-width: 860px){
  .re-aside-layout > .re-aside .re-aside-identity{flex-direction:row;align-items:center;gap:var(--re-space-3)}
  .re-aside-layout > .re-aside .re-aside-identity > :not(.re-avatar){flex:1 1 auto}
}
/* the fold-out affordance, hidden until the panel is actually foldable */
.re-toc-chevron{display:none;width:14px;height:14px;flex-shrink:0;color:var(--muted-foreground);transition:transform .2s}
.re-toc.open > .re-toc-title .re-toc-chevron{transform:rotate(90deg)}

@media (max-width:860px){
  /* Stacked, the 40px column gap is a side-by-side measure — too much between a folded
     one-line panel and the heading under it. One spacing owns the distance: the row's gap.
     Measured before: margin-bottom 20 + row-gap 40 = 60px under the TOC, which is what made
     the panel read as detached from the article it belongs to. */
  /* align-items:stretch, not the row's flex-start: in a column flex-start sizes each child to
     fit-content, and fit-content cannot go below min-content — so one wide descendant (a
     re-diagram graph with a min-width) grew the article to its own width and the whole page
     scrolled sideways. Measured at 390px: scrollWidth 1672 with flex-start, 390 with stretch.
     The TOC is unaffected: it sets its own align-self, and re-toc-collapse stretches anyway. */
  .re-toc-layout{flex-direction:column;align-items:stretch;gap:var(--re-space-4)}
  .re-toc-collapse{
    order:-1;                       /* above the article, not after it */
    /* width:auto, not 100%: a fixed 100% is measured against the container's content box, so
       the negative margins below would slide the card left instead of widening it — measured,
       the frame sat 5px from the left edge and 43px from the right. Auto lets the flex line
       stretch it, and the margins then pull both edges out equally. */
    position:static;width:auto;align-self:stretch;
    border:1px solid var(--border);border-radius:var(--radius-surface);
    background:var(--card);
    margin-bottom:0;                /* the row's gap is the only spacing here */
    /* BLED, the same way an re-section-bleed section is: the card's frame steps outside the
       content column so the card's TEXT lands on it. Without this the panel read as indented
       against the h1 right under it — measured, its label sat at 63px to the heading's 24px.
       Pulled by --re-label-gutter (inset + the 1px border), capped at the page gutter so it
       can never cross into horizontal scroll. */
    margin-inline:calc(-1 * min(var(--re-label-gutter), var(--re-gutter)));
  }
  .re-toc-collapse > .re-toc-title{
    display:flex;width:100%;margin:0;
    min-height:var(--control-h-md);
    /* --re-box-inset, NOT --re-label-gutter: the card was already pulled out by the gutter
       (inset + border), so adding the border again here counted it twice — measured, the
       label landed at 25px against a heading at 24px. */
    position:relative;padding:0 var(--re-box-inset);
    background:none;border:0;font-family:inherit;cursor:pointer;
    color:var(--muted-foreground);
  }
  /* The icon stays INLINE, and the panel keeps the geometry it has beside an article: icon
     first, label after it, the list's rail on the icon's centre, links past the rail. That
     trio is the component's own standard and the card does not get to break it.

     What the bleed aligns, then, is the ICON column — it lands on the container edge, so the
     card's first mark and the heading below it share an x, and everything inside the card
     stays internally consistent (label text and link text on one column of their own).

     Two things were tried and measured first. Putting the icon in a reserved slot inside the
     bleed strip aligned the LABEL at the cost of the icon: 19px of strip against a 13px icon
     left it at 6px, jammed on the frame, and at data-radius="lg"/"full" the corner curve cuts
     through it. Dropping the icon aligned the label cleanly but threw away the trio — and
     then the rail, having nothing to sit under, floated out at -9px on its own. */
  .re-toc-collapse > .re-toc-title > :last-child{margin-left:auto}
  .re-toc-collapse .re-toc-chevron{display:block}
  /* the rail keeps its own offset — 6px lands it on the 13px icon's centre, exactly as it
     does beside an article; the card only shifts where that whole group starts */
  .re-toc-collapse > .re-toc-list{
    display:none;
    margin:0 var(--re-box-inset) var(--re-space-3);
    margin-left:calc(var(--re-box-inset) + 6px);
  }
  .re-toc-collapse.open > .re-toc-list{display:flex}
}

/* ── Multi-select dropdown ──────────────────────────────────── */
.re-ms-wrap{position:relative;display:inline-block}

/* ── re-filterbar — search + facets + group-by + the app's view control, one row (TF1, 2026-10-01) ──
   Drawn by reFilterbar() from one state object, so every app filters the same way (tasks first; contacts,
   notes, split next). Order is fixed: search · facets · [push] · group-by · the app's end slot (a view
   re-segment). Every control is --control-h-md, the height re-btn and re-search-row already share, so the
   row is one line with no hand-tuned numbers.
   Folding is the Search + Filter pattern's rule, "does it fit", and CSS cannot count: a fixed breakpoint
   was measured wrong at once (640px: at 768 three facets + group-by left the search 103px wide). So
   reFilterbar measures. When the search would drop under 200px the facets and group-by fold into one
   "Filter · N" button and a sheet drawn from the same state; they unfold once the bar is wide enough
   again (.re-filterbar-folded). A strip that has to scroll to be seen is a filter people forget they set. */
.re-filterbar{width:100%}
.re-filterbar-row{display:flex;align-items:center;gap:var(--re-space-2);min-width:0}
.re-filterbar-search{flex:1 1 200px;min-width:0}
.re-filterbar-facets,.re-filterbar-end{display:flex;align-items:center;gap:var(--re-space-2);flex-shrink:0}
.re-filterbar-end{margin-left:auto}
.re-filterbar .re-ms-trigger{height:var(--control-h-md);padding:0 calc(var(--pad-x) * .85);font-size:calc(0.8125rem * var(--font-scale));font-weight:500}
.re-filterbar .re-ms-trigger .re-filterbar-count{color:inherit;opacity:.8}
.re-filterbar .re-ms-panel{z-index:30}
.re-filterbar .re-ms-panel.re-filterbar-panel-end{left:auto;right:0}
.re-filterbar .re-ms-item{width:100%;border:0;background:none;font-family:inherit;text-align:left}
.re-filterbar-more{display:none}
.re-filterbar-folded .re-filterbar-facets,.re-filterbar-folded .re-filterbar-group{display:none}
.re-filterbar-folded .re-filterbar-more{display:inline-flex}
/* The folded sheet: one accordion row per facet (name · what is picked · chevron), the bar's own dropdown list inside.
   Rows run edge to edge of the sheet body, like a re-row-list inside a sheet; the picked summary is muted and truncates. */
.re-filterbar-sheet-list{margin:-14px calc(-1 * var(--re-sheet-inset,16px));border-bottom:1px solid var(--border)}
.re-filterbar-sheet-list .re-ac-trigger{padding-inline:var(--re-sheet-inset,16px);font-size:calc(0.875rem * var(--font-scale))}
.re-filterbar-sheet-list .re-ac-trigger:disabled{opacity:.45;cursor:not-allowed}
.re-filterbar-sheet-sum{flex:1;min-width:0;margin-left:auto;text-align:right;color:var(--muted-foreground);font-weight:400;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-filterbar-sheet-list .re-ms-list{max-height:none;padding-block:4px}
.re-filterbar-sheet-list .re-ms-item{width:100%;border:0;background:none;font-family:inherit;text-align:left;padding-inline:var(--re-sheet-inset,16px)}
.re-filterbar-sheet-hint{margin:0;padding:0 var(--re-sheet-inset,16px) 10px}
/* stacked (JS, second step): the search on its own full line, the Filters button and the view control under it */
.re-filterbar-stacked .re-filterbar-row{flex-wrap:wrap;row-gap:var(--re-space-2)}
.re-filterbar-stacked .re-filterbar-search{flex:1 1 100%}
.re-ms-trigger{display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:var(--radius-control);border:1px solid var(--border);background:transparent;cursor:pointer;font-size:12px;color:var(--foreground);font-family:inherit;white-space:nowrap}
.re-ms-trigger.applied{border-color:var(--primary);color:var(--primary);background:color-mix(in oklch,var(--primary) 8%,transparent)}
.re-ms-trigger svg{color:var(--muted-foreground);flex-shrink:0}
.re-ms-panel{position:absolute;top:calc(100% + 4px);left:0;min-width:220px;max-width:320px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 4px 16px color-mix(in oklch,var(--foreground) 8%,transparent);z-index:300;display:none}
.re-ms-panel.open{display:block}
.re-ms-search-row{padding:7px 7px 4px;border-bottom:1px solid var(--border);position:relative}
.re-ms-search-row svg{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--muted-foreground);pointer-events:none}
.re-ms-search{width:100%;padding:5px 8px 5px 28px;border-radius:calc(var(--radius) - 2px);border:1px solid var(--border);background:var(--muted);color:var(--foreground);font-size:12px;font-family:inherit;outline:none}
.re-ms-clear{display:flex;align-items:center;gap:6px;width:100%;padding:5px 10px;background:none;border:none;border-bottom:1px solid var(--border);font-size:12px;color:var(--muted-foreground);cursor:pointer;font-family:inherit;text-align:left}
.re-ms-clear:hover{color:var(--foreground)}
/* Scrollbar: the shared one (base.css `.re-scroll` block names re-ms-list in its :is() list).
   This was a byte-identical copy of those nine declarations and reserved 11px instead of the
   4px it asked for, for the reason explained there. */
.re-ms-list{max-height:200px;overflow-y:auto}
.re-ms-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;font-size:13px;color:var(--foreground);cursor:pointer;gap:8px}
/* Disabled and still visible (TK2): the control says the filter does not apply right now instead of vanishing. */
/* A long option stays one line and ends in "…" once the panel reaches 320px (TK2: "Google · Inofab Health · My Tasks"
   wrapped). Needs the label in its own element; the full text is the item's title. */
.re-ms-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-ms-trigger:disabled,.re-ms-item:disabled{opacity:.45;cursor:not-allowed}
.re-ms-item:disabled:hover{background:none}
.re-chip:disabled{opacity:.45;cursor:not-allowed}
.re-ms-item:hover{background:var(--muted)}
.re-ms-item .re-ms-check{width:14px;height:14px;color:var(--primary);flex-shrink:0;opacity:0}
.re-ms-item.sel .re-ms-check{opacity:1}
.re-ms-item.sel{color:var(--primary)}

/* ── Empty state ────────────────────────────────────────────── */
.re-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--re-space-3);padding:40px 24px;text-align:center}
.re-empty-state-icon{color:var(--muted-foreground);opacity:.65}
.re-empty-state-title{font-size:calc(0.9375rem * var(--font-scale));font-weight:600;color:var(--foreground)}
.re-empty-state-text{margin:0;font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground);max-width:34ch}  /* margin:0 — canonically <p>, and the UA's p-margin was landing on TOP of the column gap, so only the title->text step grew (measured at 390px, gap 15: icon->title 15, title->text 28, text->action 19). Third time this bit the same file (re-banner, then re-hint at re-hint's own comment). Audited the rest: every other DS class written on a <p> in this repo either sets its own margin (re-colophon, deliberately) or inherits margin:0 from its base (re-hint-warning) — this was the only one left, so no family rule and no gate. (labs §V19-1) */
.re-empty-state-action{margin-top:var(--re-space-1)}
/* re-empty-state has NO frame of its own — it centres an icon, a message and an optional
   action, and nothing else. So it belongs where the content it replaces was: inside the
   re-row-list, the re-card or the panel, keeping that container's frame and background.
   As a SIBLING of a list it reads as a failure: the list with every row hidden collapses to
   a 1px-tall bordered box (a thick line) and the message floats underneath it with no
   surface. That is what a consumer shipped, and it looked like a rendering bug.
   Inside the list it is the list's only visible child, so re-row-list's own
   :nth-child(1 of :not([hidden])) rules give it the box's top AND bottom radius. */
.re-row-list > .re-empty-state{background:transparent}

/* ── Skeleton ───────────────────────────────────────────────── */
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.re-skeleton{background:color-mix(in oklch,var(--foreground) 10%,transparent);border-radius:calc(var(--radius-inset) - 2px);animation:pulse 1.5s ease-in-out infinite}

/* ── Inline code ────────────────────────────────────────────── */
/* Inline code chip. NOTE this is a bare element selector in the components layer, so it
   beats a consumer's own layered `code` rule whatever its specificity (see the cascade-layer
   note in base.css) — keep it conservative.
   line-height + box-decoration-break: an inline chip with vertical padding paints its box on
   EVERY line it wraps to; without a line-height of its own those boxes overlap in tight
   containers (table cells, which default to line-height:normal). clone gives each fragment
   its own padding and radius. */
code{font-family:var(--font-mono);font-size:11.5px;line-height:1.35;background:color-mix(in oklch,var(--foreground) 8%,transparent);padding:1px 6px;border-radius:calc(var(--radius-inset) - 2px);color:var(--primary);-webkit-box-decoration-break:clone;box-decoration-break:clone}
/* A fenced block is not a chip: strip the chip treatment inside pre. Lives here, in the same
   layer as the rule above, because a consumer's own reset would lose to it. */
pre code{background:none;padding:0;border-radius:0;font-size:inherit;line-height:inherit;color:inherit;-webkit-box-decoration-break:slice;box-decoration-break:slice}

/* ── Code Block ─────────────────────────────────────────────── */
.re-code-block{background:color-mix(in oklch,var(--foreground) 5%,transparent);border:1px solid var(--border-surface);border-radius:var(--radius);padding:14px 16px;font-family:var(--font-mono);font-size:12.5px;line-height:1.7;overflow-x:auto;overflow-y:auto;color:var(--foreground);white-space:pre;display:block;margin:0}
.re-code-block-wrap{position:relative}
/* A command to copy, not a listing to read: it breaks onto as many lines as it needs instead of scrolling sideways.
   One scrolling line cut the start of the command once its copy button or a narrow screen moved it ("laude mcp add…",
   id's token page, 2026-10-05). Breaks anywhere because a URL or a token has no spaces to break at. */
.re-code-block-break{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden}
/* The copy button belongs to the block (CB1, id 2026-10-05): top-right, and the block keeps its lines clear of it. A
   breaking block wrapped its text under the button; a scrolling one slid text beneath it. The pad is the button's width
   plus the block's own 16px, so the last character stops where the button's gutter starts. */
.re-code-block-copy{position:absolute;top:8px;right:8px}
.re-code-block-wrap:has(> .re-code-block-copy) > .re-code-block{padding-right:calc(var(--re-ctl-sm-h) + 16px)}
.re-code-block-wrap .code-lang{position:absolute;top:8px;right:10px;font-family:var(--font-mono);font-size:10px;color:var(--muted-foreground);text-transform:uppercase;letter-spacing:.06em}

/* ── Inline Value / Val ─────────────────────────────────────── */
/* 0.9em, not 11px: this is a chip INSIDE a sentence, so it has to follow the line it sits in
   — at 11px it read as a footnote next to 15px body text, and it ignored --font-scale, so
   density moved the sentence and left the value behind. 0.9 is the usual mono-in-prose
   correction (a monospace face reads larger than a proportional one at equal size). */
.re-val{font-family:var(--font-mono);font-size:0.9em;background:color-mix(in oklch,var(--foreground) 7%,transparent);border:1px solid color-mix(in oklch,var(--foreground) 15%,transparent);padding:1px 5px;border-radius:3px;color:var(--accent);white-space:nowrap;display:inline}

/* ── Step Block ─────────────────────────────────────────────── */
.re-step-block{border:1px solid var(--border-surface);border-radius:var(--radius);overflow:hidden;margin-bottom:10px}
.re-step-header{background:color-mix(in oklch,var(--foreground) 4%,transparent);border-bottom:1px solid var(--border);padding:calc(8px * var(--density-mult,1)) calc(14px * var(--density-mult,1));display:flex;align-items:center;gap:10px}
.re-step-num{font-family:var(--font-mono);font-size:10px;font-weight:600;color:var(--primary);background:color-mix(in oklch,var(--primary) 12%,transparent);border:1px solid color-mix(in oklch,var(--primary) 30%,transparent);padding:2px 7px;border-radius:calc(var(--radius-inset) - 2px);flex-shrink:0}
.re-step-title{font-size:13px;font-weight:600;color:var(--foreground);flex:1}
.re-step-where{font-family:var(--font-mono);font-size:10px;color:var(--muted-foreground)}
.re-step-body{padding:calc(14px * var(--density-mult,1)) calc(16px * var(--density-mult,1))}

/* ── Checklist (visual, non-form) ───────────────────────────── */
.re-checklist{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:4px}  /* v8 §V8-1 — margin/padding, not just list-style: the DS ships no global reset (it relies on the host page's), so on a page without one the UA's ul defaults leaked through and a checklist drew 40px indented, 16px out of rhythm. Its siblings re-note-title/-body already zero their tag's margin; this was the family's only tag-bound member that didn't. */
.re-checklist li{display:flex;align-items:flex-start;gap:8px;font-size:13px;color:var(--foreground);padding:2px 0}
.re-checklist li::before{content:'☐';font-size:19px;line-height:16px;color:var(--muted-foreground);flex-shrink:0;font-family:var(--font-mono)}
.re-checklist li.done{color:var(--muted-foreground)}
.re-checklist li.done::before{content:'☑';font-size:19px;line-height:16px;color:var(--success)}

/* ── Search ─────────────────────────────────────────────────── */
.re-search-wrap{position:relative;width:100%}
/* padding-inline is --re-box-inset, not a number: a search row IS a field, and re-input
   already reads that token so a text field's first character lands on the box text column.
   This one hardcoded 10px, so its icon sat 8px left of every other field, label and row in
   the same column — measured on patterns/search-filter.html: icon 49px, everything else 57.
   The vertical 7px stays a number; it pairs with this row's own height, not with a column. */
/* A CONTROL height, not a derived one. It used to be `padding:7px` plus whatever the line box
   came to — 36px at standard density, a number that matches no --control-h-* token (sm 32,
   md 42), so a search row and the button beside it could not be made equal in ANY
   combination. `re-items-stretch` does not save it either: re-btn carries an explicit height
   that beats align-self. Reported twice, by two consumers, five weeks apart (labs §N9-2:
   `notes` 2026-08-23, `calendar` 2026-09-07), both with the same local line — and no build
   check could have caught it, because the DS simply never wrote the property.
   A search row with a button beside it is the CANONICAL pairing, not an edge case, so the row
   reads the same token the button does and --re-search-h is the knob. Equalising downward at
   32 was measured and rejected: 16px form text plus its padding lands exactly on 32 with zero
   breathing room, and the coarse-pointer tokens go UP (2rem -> 2.15rem), not down. */
.re-search-row{--re-search-h:var(--control-h-md);height:var(--re-search-h);display:flex;align-items:center;gap:8px;padding:0 var(--re-box-inset);border:1px solid var(--border);border-radius:var(--radius-control);background:var(--card);transition:border-color .15s}
.re-search-row-sm{--re-search-h:var(--control-h-sm)}   /* pairs with re-btn-sm, the way re-search-row pairs with re-btn */
.re-search-row:focus-within{border-color:var(--primary)}
.re-search-row-icon{color:var(--muted-foreground);flex-shrink:0;display:flex}
.re-search-input{flex:1;background:none;border:none;outline:none;font-size:13px;color:var(--foreground);font-family:var(--font-sans)}
.re-search-input::placeholder{color:var(--muted-foreground)}
/* <input type="search"> comes with the engine's OWN clear button, so a search row that also
   ships re-search-clear draws TWO x's the moment you type — measured on the search+filter
   pattern. The DS owns this affordance (it is the one that knows the row's icon size and its
   visible state), so the native one goes. `appearance:none` on the pseudo-element is the only
   way to remove it; hiding it with display:none is ignored in WebKit. */
.re-search-input::-webkit-search-cancel-button,
.re-search-input::-webkit-search-decoration,
.re-input[type="search"]::-webkit-search-cancel-button,
.re-input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;appearance:none}
.re-search-clear{display:none;align-items:center;justify-content:center;width:18px;height:18px;border-radius:3px;border:none;background:none;cursor:pointer;color:var(--muted-foreground);flex-shrink:0;padding:0}
.re-search-clear:hover{background:var(--muted);color:var(--foreground)}
.re-search-clear.visible{display:flex}
.re-search-kbd{font-size:10px;font-family:var(--font-mono);color:var(--muted-foreground);background:var(--muted);border:1px solid var(--border);border-radius:3px;padding:1px 5px;white-space:nowrap;flex-shrink:0}
.re-search-panel{position:absolute;top:calc(100% + 6px);left:0;right:0;background:var(--card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 8px 24px color-mix(in oklch,var(--foreground) 8%,transparent);z-index:300;display:none;overflow:hidden}
.re-search-panel.open{display:block}
.re-search-panel .re-scroll-y{max-height:380px;padding:5px 0}
/* Sheet variant — on phones the absolute dropdown gets pushed under the keyboard;
   pin it to the bottom of the viewport instead (full width, taller results). */
@media (max-width:767px){
  .re-search-panel.re-search-sheet{position:fixed;left:0;right:0;bottom:0;top:auto;border-radius:var(--radius-surface) var(--radius-surface) 0 0;box-shadow:var(--shadow-lg);padding-bottom:env(safe-area-inset-bottom)}
  .re-search-panel.re-search-sheet .re-scroll-y{max-height:60dvh}
}
.re-search-group-label{margin:0;padding:8px 12px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-foreground)}
.re-search-item{display:flex;align-items:center;gap:10px;padding:7px 12px;font-size:13px;color:var(--foreground);cursor:pointer;background:none;border:none;width:100%;text-align:left;font-family:var(--font-sans);text-decoration:none}
.re-search-item:hover,.re-search-item.active{background:var(--muted)}
.re-search-item-icon{width:14px;height:14px;flex-shrink:0;color:var(--muted-foreground)}
.re-search-item-label{flex:1;font-weight:500}
.re-search-item-meta{font-size:11px;color:var(--muted-foreground)}
.re-search-divider{height:1px;background:var(--border);margin:4px 0}
.re-search-empty{padding:24px 12px;text-align:center;font-size:13px;color:var(--muted-foreground)}

/* ── Command palette (re-search-modal) — the search dropdown, expanded into a
   centered ⌘K modal. Reuses re-search-group-label / re-search-item for results. */
.re-search-modal-backdrop{position:fixed;inset:0;background:color-mix(in oklch,var(--foreground) 30%,transparent);z-index:400;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.re-search-modal-backdrop.open{display:flex}
.re-search-modal{width:min(560px,92vw);background:var(--card);border:1px solid var(--border-elevated);border-radius:var(--radius-surface);box-shadow:var(--shadow-lg);overflow:hidden}
.re-search-modal-row{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--border)}
.re-search-modal-row svg{color:var(--muted-foreground);flex-shrink:0}
.re-search-modal-row input{flex:1;border:none;background:transparent;font-size:15px;font-family:inherit;color:var(--foreground);outline:none}
.re-search-modal-results{max-height:min(52vh,420px);overflow-y:auto;padding:6px 0}
.re-search-modal-hint{display:flex;gap:14px;padding:11px 16px;font-size:11.5px;color:var(--muted-foreground);border-top:1px solid var(--border)}

/* ── Lists ──────────────────────────────────────────────────── */
.re-list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
.re-list li{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:var(--foreground);line-height:1.6}
.re-list-bullet li::before{content:'';width:5px;height:5px;border-radius:50%;background:var(--primary);flex-shrink:0;margin-top:8px}
.re-list-numbered{counter-reset:re-counter}
.re-list-numbered li{counter-increment:re-counter}
.re-list-numbered li::before{content:counter(re-counter);min-width:18px;height:18px;border-radius:50%;background:color-mix(in oklch,var(--primary) 12%,transparent);border:1px solid color-mix(in oklch,var(--primary) 25%,transparent);color:var(--primary);font-size:10px;font-weight:600;font-family:var(--font-mono);display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
.re-list-sm li{font-size:12px;gap:8px}.re-list-sm.re-list-bullet li::before{width:4px;height:4px;margin-top:7px}
.re-list-lg li{font-size:14px;gap:12px}.re-list-lg.re-list-bullet li::before{width:6px;height:6px;margin-top:8px}

/* ── Accordion ──────────────────────────────────────────────── */
.re-accordion{border:1px solid var(--border-surface);border-radius:var(--radius);overflow:hidden;background:var(--card)}
.re-ac-item{border-bottom:1px solid var(--border)}
.re-ac-item:last-child{border-bottom:none}
.re-ac-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;padding:calc(12px * var(--density-mult,1)) calc(16px * var(--density-mult,1));background:none;border:none;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:var(--foreground);text-align:left;gap:8px}
.re-ac-trigger:hover,.re-ac-trigger.open{background:color-mix(in oklch,var(--foreground) 3%,transparent)}
/* OPEN keeps the hover tint and gains a light line under it: the answer read as glued to its
   question, 2px from the tint to the first line of text (Ersin, redev-id Yardım, 2026-10-01). The
   line is the open re-row-group's inner line, the list's border thinned toward transparent, so
   the item reads as one block and the line does not compete with the item borders. */
.re-ac-trigger.open{border-bottom:1px solid color-mix(in oklab,var(--border) 55%,transparent)}
/* Leading slot — group a checkbox/icon with the label. Use a div[role="button"]
   tabindex="0" trigger (not <button>) when nesting an interactive control; the
   control should stopPropagation so it doesn't toggle the panel. */
.re-ac-lead{display:inline-flex;align-items:center;gap:10px;min-width:0}
.re-ac-chevron{flex-shrink:0;color:var(--muted-foreground);transition:transform .2s;width:14px;height:14px}
.re-ac-trigger.open .re-ac-chevron{transform:rotate(90deg)}
/* ⚠ The markup must draw `chevron-RIGHT`. This rotation is the DS convention for every
   expander (re-tree-toggle · re-nav-chevron · re-sec-chevron · re-row-group-caret), and it
   turns right -> down. Drawn pointing down instead, the same 90deg takes it down -> LEFT,
   which is what the accordion shipped with: the CSS was innocent, the glyph was wrong, and
   a rotation cannot tell. `pnpm check:icons` reads the family out of this file and checks
   the markup against it. */
.re-ac-content{max-height:0;overflow:hidden;transition:max-height .25s ease,opacity .2s ease;opacity:0}
.re-ac-content.open{max-height:600px;opacity:1}
/* Same space above the answer as below it, measured text-to-edge (the line's half-leading counts on both sides). */
.re-ac-body{padding:calc(14px * var(--density-mult,1)) calc(16px * var(--density-mult,1));font-size:13px;color:var(--muted-foreground);line-height:1.6}
/* re-meta-list — facts about a thing, as label · value pairs, WITHOUT row borders (KY1, Ersin 2026-10-06: a note's Source showed
   two bordered row lists, a section label and an accordion inside a row list inside an accordion — "çok iç içe row, çok fazla
   border"). A <dl>: label column as wide as its longest label, values from one x. Meant for the body of an accordion or a card,
   where the box around it already exists; a row list there draws a second box. */
.re-meta-list{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:6px 16px;margin:0;font-size:calc(0.8125rem * var(--font-scale));line-height:1.5}
.re-meta-list > dt{margin:0;color:var(--muted-foreground)}
.re-meta-list > dd{margin:0;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px;color:var(--foreground);overflow-wrap:anywhere}
/* re-ac-flush — a part of an open accordion that runs edge to edge (a code block): no inset, no radius of its own, one line
   above it. The accordion's own clip rounds its bottom corners. A code block inside loses its frame: the accordion IS the frame. */
.re-ac-flush{border-top:1px solid color-mix(in oklab,var(--border) 55%,transparent)}
.re-ac-flush[hidden]{display:none}
.re-ac-flush .re-code-block{margin:0;border:0;border-radius:0;max-height:min(50vh,360px)}
.re-ac-flush > .re-row-list{border:0;border-radius:0;background:none}   /* a list of records in an accordion (tasks roadmap › Tarihsizler) */
/* The flush part's head: a band in another tone with a bold title and its action at the end (copy). It is NOT a toggle: the part
   is already inside a closed accordion, a second open/close would be a second accordion format and one more tap (Ersin, KY1: the
   text button with a chevron "tıklanıyor hissi yaratıyor"). The band says what the part is, the action is the only control. */
.re-ac-flush-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:40px;
  padding:4px calc(8px * var(--density-mult,1)) 4px calc(16px * var(--density-mult,1));
  background:color-mix(in oklch,var(--foreground) 4%,transparent);border-bottom:1px solid color-mix(in oklab,var(--border) 55%,transparent);
  font-size:calc(0.75rem * var(--font-scale));font-weight:650;color:var(--foreground)}

/* ── Progress Steps (horizontal segmented) ──────────────────── */
.re-progress-steps{display:flex;gap:3px;margin-bottom:8px}
.re-progress-step{flex:1;height:4px;border-radius:2px;background:var(--muted)}
.re-progress-step.done{background:var(--_solid,var(--success))}   /* v3 V2 — reads --_solid so re-c-{role} can recolor for consumption ("5/5 used" in danger); default unchanged */
.re-progress-step.active{background:var(--_solid,var(--primary))}
/* v3 V2 (öneri-2) — usage/quota mode: filled steps grade toward warning as they near the
   limit, by POSITION (last=danger, penultimate=warning, earlier=success), independent of the
   total. Only .done steps color, so "3/5 used" is all-green (safe) and "5/5" ends in red. */
.re-progress-steps-usage .re-progress-step.done{background:var(--success)}
.re-progress-steps-usage .re-progress-step.done:nth-last-child(2){background:var(--warning)}
.re-progress-steps-usage .re-progress-step.done:last-child{background:var(--danger)}
/* Compact — just the bars + a "3/5" counter; the default past ~5 steps, where
   per-step labels get too narrow to read on a phone. */
.re-progress-steps-compact{display:flex;align-items:center;gap:12px}
.re-progress-steps-compact .re-progress-steps{flex:1;margin-bottom:0}
/* the 8px above is for standalone/block use; in a gap-managed container it doubles.
   The rule that does this is now ONE rule for the whole family — see "the parent owns the
   rhythm" below, which is where re-progress-steps is listed. */
/* ══ The parent owns the rhythm ═══════════════════════════════════════════════
   A class that separates itself from the thing below it with a bottom margin is correct on
   its own and wrong inside a container that already sets `gap` — the two add up. Measured on
   the reported case (labs v12 §V12-2): re-input-label's 6px on top of re-sheet-body's 16px
   made the distance 22px in a column whose rhythm is 16.

   The DS had already solved this once, for re-progress-steps, and written the reason down —
   but as a rule naming ONE member. That is why it came back: the family had four members and
   the list had one, so the next member was a new bug rather than a covered case. This is the
   whole family in one place, so adding a fifth is one edit.

   ⚠ Only containers that actually set `gap` belong on the left. `re-wizard-w` was in the
   original list and sets none — a container that cannot cause the problem is noise, and it
   implies a rhythm it does not manage. `re-section-block` DOES set one (v0.28.0) and was
   missing; that is the same omission repeating, one version later. */
/* ⚠ `:where()` on the LEFT, `:is()` on the RIGHT, and the difference decides whether this
   rule does anything at all. `:where()` contributes ZERO specificity, so wrapping the members
   in it made the whole selector 0,0,0 — losing to `.re-input-label{margin-bottom:6px}` at
   0,1,0. Measured: margin-bottom stayed 6px in all four containers. `:is()` takes the
   specificity of its most specific argument (0,1,0 here), which ties with the base rule and
   wins on order. The containers stay in `:where()` because they must NOT add weight — a
   consumer overriding this needs to beat one class, not two.

   ⚠ The gap utilities are qualified with .re-flex / .re-grid, not matched bare. `gap` does
   nothing on a block box, so a bare `[class*="re-gap-"]` container matched an element whose
   gap was inert — the margin was dropped and NOTHING replaced it. Measured: distance 0px on
   a `re-gap-4` div with no display utility. The original rule had the same bare match with
   one member; extending it to four made a latent hazard four times as likely. */
:where(.re-row-body, .re-sheet-body, .re-section-block,
       .re-flex[class*="re-gap-"], .re-grid[class*="re-gap-"])
  > :is(.re-progress-steps, .re-input-label, .re-step-block, .re-progress-row){margin-bottom:0}

.re-progress-count{flex-shrink:0;font-size:calc(0.75rem * var(--font-scale));font-weight:600;font-family:var(--font-mono);color:var(--muted-foreground);white-space:nowrap}

/* ── re-link — brand-accent inline link ────────────────────────────────
   Reads --c-link so it stays coherent with the active brand accent. */
/* v10 §V10-3 — a DS surface used as an <a>. Anything with cursor:pointer is a candidate for
   a real link (a calendar event opening a route, a row, a card), and then the UA's own
   a:-webkit-any-link wins on properties the component never wrote: measured on re-cal-event,
   colour came out rgb(0,0,238) with an underline.

   ONE list, and `:where()` is what makes that safe. v0.20.0 shipped it with `:is()` and that
   was a regression a consumer's user spotted the same day: `a:is(…)` is 0,1,1, HIGHER than a
   component's own 0,1,0, so `color: inherit` overrode every filled surface's own foreground.
   Measured then, in dark theme: button.re-fab got --primary-foreground, a.re-fab got
   --foreground; in light theme that is the black label on a primary FAB the user asked about.

   `:where()` contributes ZERO specificity, so `a:where(…)` is (0,0,1) — the element alone.
   A class that writes its own `color` (0,1,0) wins; a surface that writes none inherits.
   The split the old wording described as two hand-kept lists happens by itself.

   ⚠ That old wording said the opposite of the code — "TWO lists… 26 of the 36 write their own
   color and must NOT be handed inherit; only the other 10 need it" — and the code has been one
   list since. The cost was measured, not hypothetical: reading this comment, TWO sessions
   independently concluded that `.re-fab` being in the list meant the bug was still live, and
   filed it. Both were corrected by measuring. Verified here at v0.49.1 with a trap colour on
   the parent (`color:red`): a.re-fab and button.re-fab both read --primary-foreground, while
   a.re-cal-event and a.re-row-tap — classes with no colour of their own — both came back red,
   i.e. `inherit` really does reach exactly the ones that need it.
   `text-decoration: none` is what they all need. (labs §N9-3) */
a:where(.re-btn,.re-chip,.re-card-tap,.re-card-interactive,.re-row-tap,.re-cal-event,.re-cal-chip,.re-cal-more,.re-day,.re-tile,.re-note,.re-note-pin,.re-badge,.re-person,.re-board-add,.re-road-bar,.re-road-milestone,.re-nav-item,.re-nav-sub,.re-nav-leaf,.re-page-tab,.re-tab,.re-tabbar-item,.re-navbar-item,.re-navbar-menu-item,.re-sec-item,.re-sec-sub,.re-dropdown-item,.re-search-item,.re-ms-item,.re-tree-node,.re-page,.re-fab,.re-check-btn,.re-task-card,.re-stat-card,.re-cal-year-month){color:inherit;text-decoration:none}
button:where(.re-btn,.re-chip,.re-card-tap,.re-card-interactive,.re-row-tap,.re-cal-event,.re-cal-chip,.re-cal-more,.re-day,.re-tile,.re-note,.re-note-pin,.re-badge,.re-person,.re-board-add,.re-road-bar,.re-road-milestone,.re-nav-item,.re-nav-sub,.re-nav-leaf,.re-page-tab,.re-tab,.re-tabbar-item,.re-navbar-item,.re-navbar-menu-item,.re-sec-item,.re-sec-sub,.re-dropdown-item,.re-search-item,.re-ms-item,.re-tree-node,.re-page,.re-fab,.re-check-btn,.re-task-card,.re-stat-card,.re-cal-year-month){border:0;background:none;font:inherit;color:inherit;text-align:inherit;appearance:none;-webkit-appearance:none}
/* And the focus ring: as an <a> these got the UA's 1px outline instead of the DS's 2px one,
   because the combined :focus-visible list below only names the controls. */
:is(a,button):where(.re-btn,.re-chip,.re-card-tap,.re-card-interactive,.re-row-tap,.re-cal-event,.re-cal-chip,.re-cal-more,.re-day,.re-tile,.re-note,.re-note-pin,.re-badge,.re-person,.re-board-add,.re-road-bar,.re-road-milestone,.re-nav-item,.re-nav-sub,.re-nav-leaf,.re-page-tab,.re-tab,.re-tabbar-item,.re-navbar-item,.re-navbar-menu-item,.re-sec-item,.re-sec-sub,.re-dropdown-item,.re-search-item,.re-ms-item,.re-tree-node,.re-page,.re-fab,.re-check-btn,.re-task-card,.re-stat-card,.re-cal-year-month):focus-visible{outline:2px solid var(--c-focus,var(--ring));outline-offset:2px}

.re-link{color:var(--c-link);text-decoration:none;cursor:pointer;transition:color .12s}
.re-link:hover{text-decoration:underline;text-underline-offset:2px;text-decoration-color:color-mix(in oklch,var(--c-link) 45%,transparent)}
.re-link-muted{color:var(--muted-foreground)}
.re-link-muted:hover{color:var(--c-link)}
/* reLinksHtml (LK1): the links found in a text, under the field that holds it. One per line, the path truncates, the icon says
   "opens elsewhere". Sized as a hint line so it does not compete with the field above. */
.re-link-list{display:flex;flex-direction:column;align-items:flex-start;gap:4px;min-width:0;max-width:100%;margin-top:6px}
.re-link-item{display:inline-flex;align-items:center;gap:6px;min-width:0;max-width:100%;font-size:calc(0.8125rem * var(--font-scale))}
.re-link-item > svg{flex-shrink:0;width:var(--re-icon);height:var(--re-icon)}
.re-link-item > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ── re-glow — ambient radial glow layer ───────────────────────────────
   Drop one (or more) inside a position:relative box. Color comes from the
   canonical dictionary (re-c-{role} → --_solid); position from re-glow-{pos};
   intensity from re-glow-soft/strong. Multiple glows = multiple .re-glow
   elements in the same box. Keep content above (position:relative;z-index:1).
   Scales with the box (size is %), so it works on big sections and small cards. */
.re-glow{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;border-radius:inherit}
/* ── The background layer, named once ─────────────────────────────────────────────────
   Content that follows a background layer lifts above it. The consumer used to write this
   per box (`> .re-card-body{position:relative;z-index:1}`); it belongs here, once.

   ⚠ The exclusion list is NOT decoration. `.re-glow ~ *` alone also catches a SECOND glow and
   an re-dots — both documented as stackable — and since all three selectors are (0,1,0),
   source order decides and the later rule wins: the second layer would be pulled into the
   content plane. Measured before the fix: glow #2 and re-dots both came out relative/z:1.

   ⚠ And the rule makes an unwritten contract MANDATORY, so it is written now:
   **re-glow and re-dots are the FIRST children of their box; content comes after them.**
   `~` only selects later siblings, so a glow placed last gets no lift applied to anything —
   and a positioned z:0 layer paints OVER unpositioned content (paint order: static inline 7,
   positioned z:0 8). The glow would cover the text.

   If you add a third background layer, add it to BOTH lists below. audit-css enforces that:
   a class that declares position:absolute + z-index:0 and is not named here fails the build,
   because a missing name here is silent and its symptom (the new layer on top of the text)
   points nowhere near this rule. */
:is(.re-glow,.re-dots,.re-rainbow) ~ *:not(.re-glow):not(.re-dots):not(.re-rainbow){position:relative;z-index:1}
.re-glow-fixed{position:fixed;border-radius:0}   /* viewport-fixed full-page ambient */
.re-glow::before{
  content:"";position:absolute;width:var(--glow-size,72%);aspect-ratio:1;border-radius:50%;
  top:50%;left:50%;transform:translate(-50%,-50%);
  background:radial-gradient(circle,
    color-mix(in oklch, var(--_solid,var(--c-glow)) var(--glow-intensity,22%), transparent) 0%,
    transparent 70%);
  filter:blur(var(--glow-blur,40px));
}
/* Position = origin of the glow within the box */
.re-glow-top::before          {top:0;    left:50%;  transform:translate(-50%,-50%)}
.re-glow-bottom::before       {top:100%; left:50%;  transform:translate(-50%,-50%)}
.re-glow-left::before         {top:50%;  left:0;    transform:translate(-50%,-50%)}
.re-glow-right::before        {top:50%;  left:100%; transform:translate(-50%,-50%)}
.re-glow-center::before       {top:50%;  left:50%;  transform:translate(-50%,-50%)}
.re-glow-top-left::before     {top:0;    left:0;    transform:translate(-50%,-50%)}
.re-glow-top-right::before    {top:0;    left:100%; transform:translate(-50%,-50%)}
.re-glow-bottom-left::before  {top:100%; left:0;    transform:translate(-50%,-50%)}
.re-glow-bottom-right::before {top:100%; left:100%; transform:translate(-50%,-50%)}
/* Intensity (color strength) */
.re-glow-soft  {--glow-intensity:14%}
.re-glow-strong{--glow-intensity:34%}
/* Size (glow radius; md = default 72%) */
.re-glow-sm{--glow-size:48%}
.re-glow-md{--glow-size:72%}
.re-glow-lg{--glow-size:104%}

/* ── re-dots — dot-grid texture layer ──────────────────────────────────
   Same layering model as re-glow: an absolute layer inside a position:relative
   box. Color from re-c-{role} (default muted). Combine with re-glow freely
   (stack both in the same box). Gap via re-dots-sm/lg or --dots-gap. */
.re-dots{position:absolute;inset:0;z-index:0;pointer-events:none;border-radius:inherit;
  background-image:radial-gradient(circle,
    color-mix(in oklch, var(--_solid,var(--muted-foreground)) var(--dots-intensity,26%), transparent) 1px,
    transparent 1px);
  background-size:var(--dots-gap,20px) var(--dots-gap,20px);
}
/* Gap (spacing between dots — the dot size stays 1px) */
.re-dots-tight{--dots-gap:14px}
.re-dots-loose{--dots-gap:28px}

/* ── re-rainbow — a soft, slowly moving colour field ──────────────────────────────────────────────────────────────────
   No image and no script: five large radial blobs on a pastel base, blurred, on a layer bigger than its box so it has room
   to move. Two motions:
     (default)            drifts on its own, slowly (a 40s ease-in-out loop) — a card, a section
     re-rainbow-scroll    pans with the page instead (CSS scroll-driven, view() timeline) — a landing stage
   A background layer like re-glow / re-dots: the FIRST child of a position:relative box, content after it. Colour comes from a
   palette (--rb-1…5 + --rb-base): by default the brand's neighbouring hues; a preset fixes it: aurora · lagoon · dusk · sunrise. data-re-rainbow="random" lets
   the DS script scatter the blobs (reRainbow). Reduced motion: still. In dark mode the field dims under the page so text stays
   readable. */
.re-rainbow{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:clip;border-radius:inherit;
  --rb-1:oklch(80% .1 255);--rb-2:oklch(87% .08 190);--rb-3:oklch(79% .12 305);--rb-4:oklch(88% .08 160);--rb-5:oklch(90% .07 75);
  --rb-base:oklch(93% .03 240)}
/* The default palette follows the BRAND (Ersin 2026-10-08: "indigo olan bir yerde kırmızı turuncu yeşil kullanmayız"): five neighbours
   of the brand's hue, from 70° below to 55° above it, light and soft, so an indigo page gets blue · violet · lavender · pink, a teal
   one green · teal · blue. Relative colour syntax; a browser without it keeps the mixed palette above (aurora). */
@supports (color: oklch(from red l c h)){
  .re-rainbow{--rb-1:oklch(from var(--brand) 80% .1 calc(h - 35));--rb-2:oklch(from var(--brand) 87% .07 calc(h + 25));
    --rb-3:oklch(from var(--brand) 78% .12 h);--rb-4:oklch(from var(--brand) 88% .07 calc(h - 70));
    --rb-5:oklch(from var(--brand) 90% .06 calc(h + 55));--rb-base:oklch(from var(--brand) 94% .03 h)}
}
.re-rainbow::before{content:"";position:absolute;inset:-25%;will-change:transform;
  background:
    radial-gradient(34% 42% at var(--rb-x1,18%) var(--rb-y1,28%),var(--rb-1),transparent 70%),
    radial-gradient(30% 40% at var(--rb-x2,46%) var(--rb-y2,72%),var(--rb-2),transparent 70%),
    radial-gradient(32% 42% at var(--rb-x3,74%) var(--rb-y3,26%),var(--rb-3),transparent 70%),
    radial-gradient(28% 36% at var(--rb-x4,86%) var(--rb-y4,78%),var(--rb-4),transparent 70%),
    radial-gradient(26% 34% at var(--rb-x5,28%) var(--rb-y5,86%),var(--rb-5),transparent 70%),
    var(--rb-base);
  filter:blur(var(--rb-blur,36px)) saturate(1.1);
  animation:re-rainbow-drift var(--rb-speed,40s) ease-in-out infinite alternate}
@keyframes re-rainbow-drift{
  0%{transform:translate3d(-6%,-4%,0) rotate(0deg) scale(1.04)}
  50%{transform:translate3d(5%,3%,0) rotate(6deg) scale(1.12)}
  100%{transform:translate3d(-2%,6%,0) rotate(-4deg) scale(1.06)}}
/* Scroll-linked: the field pans as its scroller scrolls — the nearest one, so it works on a landing (the page
   scrolls) and in an app shell (main scrolls). It must not be trapped: overflow:hidden makes a box a scroll container, and a
   timeline that reads a box which never scrolls stands still (measured: translateX 0 at every scroll, with view() and with
   scroll(root) in the docs shell). So the layer and the box that holds it clip with overflow:CLIP, which clips the same and is not
   a scroll container; the DS sets it on the holder. Without scroll timelines the field drifts. */
:has(> .re-rainbow-scroll){overflow:clip}
@supports (animation-timeline: scroll()){
  .re-rainbow-scroll::before{animation:re-rainbow-pan linear both;animation-timeline:scroll(nearest);animation-range:0% 100%}
  @keyframes re-rainbow-pan{from{transform:translate3d(-18%,0,0) scale(1.1)}to{transform:translate3d(18%,0,0) rotate(8deg) scale(1.1)}}
}
@media (prefers-reduced-motion:reduce){.re-rainbow::before{animation:none}}
/* Placement: only the lower or upper part of the box (a card whose content sits above or below the colour). */
.re-rainbow-bottom{-webkit-mask-image:linear-gradient(to top,#000 0,#000 30%,transparent 72%);mask-image:linear-gradient(to top,#000 0,#000 30%,transparent 72%)}
.re-rainbow-top{-webkit-mask-image:linear-gradient(to bottom,#000 0,#000 30%,transparent 72%);mask-image:linear-gradient(to bottom,#000 0,#000 30%,transparent 72%)}
/* Fade: only the rim melts into the page ground; the field stays vivid inside. */
.re-rainbow-fade::after{content:"";position:absolute;inset:0;border-radius:inherit;
  box-shadow:inset 0 0 var(--rb-fade,36px) calc(var(--rb-fade,36px) / 4) var(--background)}
/* Presets — a fixed palette instead of the brand's. Aurora is the mixed one (blue, mint, lavender, green, peach). */
.re-rainbow-aurora{--rb-1:oklch(80% .1 255);--rb-2:oklch(87% .08 190);--rb-3:oklch(79% .12 305);--rb-4:oklch(88% .08 160);--rb-5:oklch(90% .07 75);--rb-base:oklch(93% .03 240)}
.re-rainbow-lagoon{--rb-1:oklch(78% .1 245);--rb-2:oklch(87% .08 195);--rb-3:oklch(83% .09 175);--rb-4:oklch(90% .06 210);--rb-5:oklch(84% .08 230);--rb-base:oklch(93% .03 215)}
.re-rainbow-dusk{--rb-1:oklch(62% .12 262);--rb-2:oklch(78% .12 300);--rb-3:oklch(80% .1 340);--rb-4:oklch(82% .08 250);--rb-5:oklch(86% .07 320);--rb-base:oklch(88% .05 280)}
.re-rainbow-sunrise{--rb-1:oklch(80% .13 60);--rb-2:oklch(88% .12 110);--rb-3:oklch(86% .09 80);--rb-4:oklch(90% .08 130);--rb-5:oklch(84% .1 40);--rb-base:oklch(95% .04 90)}
:is(.dark,.dark *) .re-rainbow{--rb-base:var(--background)}
:is(.dark,.dark *) .re-rainbow::before{opacity:.42}

/* ── re-ring-glass — a thick see-through rim around a card or an icon chip ───────────────
   The ring is the wrapper's own padding: a frosted white at low alpha with a hairline, so whatever sits behind (a rainbow, a
   photo) shows through it. The inner card keeps its radius; the ring's radius is the card's plus the ring, so the two curves
   stay parallel. --re-ring-w sets the thickness (8px), --re-ring-r the inner radius. (Not --ring: that is the theme's focus-ring
   COLOUR, and a colour inside calc() made the radius invalid, 0px — measured.) */
.re-ring-glass{--_ring:var(--re-ring-w,8px);--_r:var(--re-ring-r,var(--radius-surface));padding:var(--_ring);border-radius:calc(var(--_r) + var(--_ring));
  background:color-mix(in oklab,var(--card) 42%,transparent);border:1px solid color-mix(in oklab,var(--card) 70%,transparent);
  -webkit-backdrop-filter:blur(12px) saturate(1.3);backdrop-filter:blur(12px) saturate(1.3);
  box-shadow:0 12px 32px -14px color-mix(in oklch,var(--foreground) 22%,transparent)}
.re-ring-glass > *{border-radius:var(--_r)}
:is(.dark,.dark *) .re-ring-glass{background:color-mix(in oklab,var(--card) 30%,transparent);border-color:color-mix(in oklab,var(--foreground) 14%,transparent)}

/* ══ LAYOUT PRIMITIVES ═══════════════════════════════════════════════════
   Composable page skeleton pieces (UIkit-inspired). Goal: build a page with
   almost no custom HTML/CSS. Density-aware via --density-mult / --font-scale.
   ════════════════════════════════════════════════════════════════════════ */

/* ── re-navbar — horizontal bar (app topbar or marketing nav) ─────────────
   Full-bleed by default (own side padding). Wrap a .container inside to cap
   width — the navbar then drops its own padding and the container becomes the
   flex row. Slots: re-navbar-left / -center / -right. */
.re-navbar{
  display:flex;
  align-items:center;
  gap:var(--re-space-3);
  height:calc(56px * var(--density-mult,1));  /* density-aware; still aligns with the sidebar brand */
  border-bottom:1px solid var(--border);
  background:var(--background);
  flex-shrink:0;
  transition:background .15s, border-color .15s;
  /* ONE height for everything in the bar. Measured before this line, in a single top bar:
     the icon toggles were 34px, the "Ask AI" button 32px and the avatar 30px — three
     hand-written numbers side by side, and the icon-only button read as oversized next to
     the avatar for exactly that reason. --control-h-sm is the DS's small control, so the bar
     borrows it instead of inventing a fourth. */
  --re-navbar-control-h:var(--control-h-sm);
}
.re-navbar:not(:has(> .container)){
  /* the SAME token the content column uses — these two used to hold their own copy of
     `20px * --density-mult`, which agreed with --re-gutter by coincidence until the gutter
     became derived. Measured then: the hamburger sat at x=20 and the h1 under it at x=24.
     A duplicated constant that happens to agree is not an alignment. */
  padding-left:calc(var(--re-gutter) + var(--re-chrome-bleed-l,0px));
  padding-right:calc(var(--re-gutter) + var(--re-chrome-bleed-r,0px));
}
.re-navbar > .container{display:flex;align-items:center;gap:var(--re-space-4);width:100%;height:100%}   /* full height so re-navbar-nav-tabs underline reaches the bar's bottom edge */

/* Position */
.re-navbar-sticky{position:sticky;top:0;z-index:200}
.re-navbar-fixed {position:fixed;top:0;left:0;right:0;z-index:200}
.re-navbar-transparent{background:transparent;border-color:transparent}

/* Slots — flex regions inside the navbar */
.re-navbar-left  {display:flex;align-items:center;gap:var(--re-space-3);flex:1;min-width:0}
.re-navbar-center{display:flex;align-items:center;gap:var(--re-space-2);flex-shrink:0}
.re-navbar-right {display:flex;align-items:center;gap:var(--re-space-2);flex-shrink:0;margin-left:auto}
.re-navbar-left + .re-navbar-right{margin-left:0}   /* left already claims the space */
/* v2 A5 — when a separate nav follows the brand cluster (brand in -left, main nav as a
   sibling), don't let -left grow and shove the nav to the right; the brand keeps its
   natural width and the nav sits beside it. -right stays pinned by its margin-left:auto. */
.re-navbar-left:has(+ .re-navbar-nav){flex:none}

/* Brand */
.re-navbar-brand{
  display:inline-flex;align-items:center;gap:8px;
  font-weight:700;font-size:calc(1rem * var(--font-scale));
  color:var(--foreground);text-decoration:none;white-space:nowrap;flex-shrink:0;
}

/* Horizontal nav list + items */
.re-navbar-nav{display:flex;align-items:center;gap:var(--re-space-1);list-style:none;margin:0;padding:0}
.re-navbar-item{
  display:inline-flex;align-items:center;gap:6px;
  height:calc(34px * var(--density-mult,1));
  padding:0 calc(12px * var(--density-mult,1));
  border-radius:var(--radius-control);
  font-size:calc(0.875rem * var(--font-scale));font-weight:500;
  color:var(--muted-foreground);text-decoration:none;cursor:pointer;white-space:nowrap;
  border:none;background:transparent;font-family:inherit;
  transition:background .12s, color .12s;
}
.re-navbar-item:hover{background:color-mix(in oklch, var(--foreground) 6%, transparent);color:var(--foreground)}
.re-navbar-item.active,
.re-navbar-item[aria-current="page"]{color:var(--foreground);background:color-mix(in oklch, var(--primary) 12%, transparent)}

/* ── re-footer — bottom chrome bar (mirror of re-navbar) ──────────────────
   Sits at the bottom of an re-shell-body (after re-main) → pinned, non-scrolling.
   Put it INSIDE re-main instead to let it scroll with the content. */
.re-footer{
  display:flex;align-items:center;gap:var(--re-space-3);
  height:calc(44px * var(--density-mult,1));flex-shrink:0;
  /* the SAME token the content column uses — these two used to hold their own copy of
     `20px * --density-mult`, which agreed with --re-gutter by coincidence until the gutter
     became derived. Measured then: the hamburger sat at x=20 and the h1 under it at x=24.
     A duplicated constant that happens to agree is not an alignment. */
  padding-left:var(--re-gutter);
  padding-right:var(--re-gutter);
  border-top:1px solid var(--border);
  background:var(--background);
  font-size:calc(0.75rem * var(--font-scale));color:var(--muted-foreground);
}
.re-footer a{color:var(--muted-foreground);text-decoration:none}
.re-footer a:hover{color:var(--foreground)}
.re-footer-right{margin-left:auto}

/* ── re-colophon — the line at the END OF CONTENT, not the chrome bar ──────────
   labs v13 §V13-2: all seven of their apps wrote this by hand, and identically —
   `<p class="re-text-xs re-text-muted" style="text-align:center">Tasks · Redev Labs · D1</p>`
   — one inline style, repeated in seven projects. That is the signature of a missing class.

   It is NOT an re-footer variant, and the distinction is the point re-footer's own comment
   half-made: re-footer is page CHROME — a 44px bar with a top border, pinned by the shell,
   holding links and status. A colophon is the last line of the content itself: centred, no
   rule, no height of its own, it scrolls with the article and ends it. Same words, different
   thing, and giving one class both behaviours is how a consumer ends up overriding three
   properties to get the other.

   Sits inside re-main, at the end of the flow. It brings its own top space because what it
   follows is content, not a sibling in a gap column. */
/* No bottom margin of its own: the page's end room (--re-main-end on re-main) sits BELOW the colophon, which is
   the content's last line. With its old 16px the two would have stacked to 64. */
.re-colophon{
  margin:calc(40px * var(--density-mult,1)) 0 0;
  text-align:center;
  font-size:calc(0.75rem * var(--font-scale));line-height:1.6;
  color:var(--muted-foreground);
}
.re-colophon a{color:inherit;text-decoration:none;border-bottom:1px solid color-mix(in oklch,var(--muted-foreground) 35%,transparent)}
.re-colophon a:hover{color:var(--foreground);border-bottom-color:currentColor}
/* the middot separator is the DS's, not the consumer's — seven projects typed it themselves,
   and a separator that has to be typed is a separator that drifts (·, |, —, and a bare dash
   were all in use). One span per fact; the rule puts the dots in. */
.re-colophon > span + span::before{content:'·';margin:0 .5em;color:color-mix(in oklch,var(--muted-foreground) 55%,transparent)}

/* ── re-main — the scrollable content region beside/below the chrome ───────
   flex:1 so it fills remaining space; min-width:0 prevents flex overflow;
   owns vertical scroll with the same thin-on-hover scrollbar as re-scroll-y. */
.re-main{
  flex:1 1 auto;min-width:0;
  overflow-y:auto;overflow-x:hidden;
  /* The scrollbar itself is the shared one — see the `.re-scroll` block in base.css, whose
     :is() list names re-main. It used to have its own copy here with a PAINTED --muted track,
     and that rationale was measured but is now superseded twice over:
       1. The track existed because the reservation was 11px of blank page and a full-bleed
          rule looked like it stopped short against it. The reservation is 4px now (the local
          copy said `::-webkit-scrollbar{width:4px}` and reserved 11, because specifying
          `scrollbar-width`/`scrollbar-color` disables the pseudo-elements in Blink — see
          base.css). At 4px there is no blank band to explain, only a muted hairline.
       2. The "rule ends against something" problem is answered properly now by
          --re-main-gutter-l/-r + re-main-bleed, which MEASURE the reservation and let chrome
          bleed into it. That is the fix; painting the track was the workaround.
     Don't re-add a local scrollbar rule here — a fourth copy is how this drifted. */
  /* No scrollbar-gutter at all — `auto`, the default. This line has now been wrong twice in
     the same direction, and the direction is "reserve space for something that is not there".
       `stable both-edges` reserved on BOTH edges, on the theory that symmetry keeps a
     full-bleed navbar (outside re-main) aligned with the content column (inside). It does
     not, and it is not even symmetric to look at: one reservation is filled by the scrollbar
     and the other is empty page. Measured at 390px — 39px of white on the left against 28px
     on the right.
       `stable` (one edge) fixed the left and kept the flaw on the right: the reservation is
     unconditional, so a pane with nothing to scroll still pays it. Measured, with the rows
     deleted and no scrollbar anywhere: clientWidth 377 of 388, right white 39 against 28 on
     the left. Reserving room for a scrollbar that is absent is the same bug as reserving room
     on an edge that never gets one.
       So the gutter is only there when the scrollbar is, and initMainGutter() MEASURES it —
     including on the transition, via a ResizeObserver, because a pane becomes scrollable when
     data arrives and not when the window resizes.
       What `stable` bought was "the column never shifts", and measuring the transition shows
     the DS gets that anyway: the left reservation is 0 in both states, and bled chrome pays
     the gutter it took, so the brand, the title and the list's left edge sit at 28px whether
     the pane scrolls or not — as long as the column is FULL WIDTH. Only the right edge moves,
     by exactly the scrollbar's width, which is where the scrollbar goes. Measured across
     delete → restore: clientWidth 388 → 377 → 388, right white 28 → 39 → 28, every left
     column 28 throughout. And every number here is 0 with macOS overlay scrollbars.
       ⚠ A CENTRED column is the exception, and the rule below is why. `margin-inline:auto`
     places the column against the content box's width, so losing a scrollbar's width off the
     right slides the whole column left by HALF of it. Measured at 1280px with a classic 11px
     bar: the full-width list stayed at 0 in both states, the centred one moved 187 → 181.5.
     A visible movement between one tab and the next, against an invisible margin — the trade
     is not symmetric, so the two cases get different answers. Reported by a consumer (labs
     §V22-1) and true of this repo's own docs site, which is a centred column. */
  overscroll-behavior-y:contain;       /* v9 §V9-2 — re-main IS the app's scroller, so its own rubber-band belongs to it: without this an overscroll CHAINS OUT of re-main and drags the shell, and pull-to-refresh can't tell a gesture from a bounce.
                                          This does NOT replace the host's html,body{overscroll-behavior:none}: that one stops the DOCUMENT's own bounce, which is a different event with a different owner (see the re-pull block). A consumer needs both — an earlier version of this comment claimed the host rule was "on the wrong element", which was wrong and made a consumer wonder whether to delete a rule they could not measure.
                                          NOT paired with -webkit-overflow-scrolling:touch — obsolete, momentum scrolling is the default since iOS 13. */
}
/* stable ONLY for a centred column — the exception documented at the end of re-main's
   scrollbar-gutter note. container-full is excluded because it is not centred (max-width:none),
   so its left edge is fixed like any full-width pane's and it would only be paying for a
   reservation it cannot use.
   ⚠ NOT scoped to a direct child, and the first version was. "A .container nested deeper
   belongs to the consumer's structure, not to the pane" sounded right and is simply false of
   every real shell: re-main's own children are re-pull, re-navbar, re-page-header and the view
   root, and the column lives inside THAT. Measured against a real shell shape at 1280px with
   an 11px bar — `:has(> …)` did not match, gutter stayed `auto`, and the column still moved
   187 -> 181.5. Two consumers reached that independently.
   The descendant form matches only what should match, measured on the same three shapes: a
   nested container-md YES, a nested container-full no, no container at all no. And if it ever
   over-matches, the cost is 4px of padding on a pane that does not scroll, against a visible
   2px jump — the same asymmetry that put this rule here, one level up.

   ⚠ BOTH-EDGES, and this reverses v0.45.0 — because the number the decision was made on
   changed, not the reasoning. `stable` reserves on ONE edge, so a centred column's `margin:auto`
   centres inside the narrowed content box while the chrome OUTSIDE re-main still centres on the
   window: the two columns split by half the bar. v0.45.0 measured that at an 11px bar and chose
   the split over `both-edges`, because 11px of blank page on the left against 11px of filled
   scrollbar on the right is geometrically symmetric and visibly not — a review saw it
   immediately. The bar is 4px now (see the base.css scrollbar block), so the same trade reads
   the other way: 4px of blank against 4px of bar, against a column offset that three separate
   reports have flagged. Measured on docs-toc at 1440px, switching only this property:
   `stable` reserves 4px and gives left 30.0 / right 34.0, centre **−2.0px**; `both-edges`
   reserves 8px and gives 32.0 / 32.0, centre **0.0px**. Chrome's line reaches the edge in both.
   ⚠ The offset is bar/2, so it is 2px at a 4px bar — the report that asked for this quoted
   −4px, which is the right-padding difference rather than the centre's. Same defect, and the
   formula in its own prose ("bar/2") is the one that matches.
   The cost is written down: the content box loses one more bar-width. `container-*` is a
   max-width, so a wide window's content does not change (960 stays 960) — the loss only shows
   where the column has not reached its max-width. (labs §V30-1) */
.re-main:has(.container:not(.container-full)){scrollbar-gutter:stable both-edges}

/* v9 §V9-3 — the home-bar gap for a shell with NO bottom furniture. re-fab and re-tabbar
   each add their own clearance (and already contain the inset), so this must not apply when
   either is present or the two would stack — hence :not(:has()) twice rather than a bare
   rule. Reported from a shell where five of seven screens have neither: the content ran to
   the bottom of the display with 24px of section padding against a ~34px home bar.
   Not measurable here: env() resolves to 0 in headless. */
/* Room to breathe at the END of a page (PE1, Ersin 2026-10-02: "content baya bottom'a yapışık bitiyor … fazladan
   bi 40-50 px nefes alma payı olsa iyi olur"). Measured before: every DS layout whose content overflows ended
   0-1px above the bottom at 1280 and 390, because re-main had no bottom padding off a phone, and no consumer wrote
   one (grep over nine apps: none). One token, here, so no app writes its own: --re-main-end, 48px × density, on
   top of the home-bar inset. FAB and tab-bar screens keep their own clearance and take the LARGER of the two, so
   the two never add up. A fill screen (re-main-fill) is a height chain and gets none. */
/* --re-main-clear: anything an APP puts over the bottom of the page (its own dock) sets it
   on re-shell-body to the distance from the bottom edge to the TOP of that furniture, home-bar inset included.
   re-main then ends 16px above it or with the end room, whichever is larger, and the app writes no padding of
   its own: heyre's prompt dock overrode this from app.css (390: 154, 1280: 94), where no audit could see it. */
.re-main{--re-main-end:calc(48px * var(--density-mult,1));padding-bottom:max(calc(var(--re-main-clear,0px) + 16px),calc(var(--re-main-end) + env(safe-area-inset-bottom)))}
.re-main-fill,.re-main:has(> .re-board-fill){padding-bottom:env(safe-area-inset-bottom)}
/* The page's last band hands its bottom padding to the end room. Four apps wrap the view in re-section-sm, whose
   24px stacked with the 48 to 72 (contacts, plan, split, one, measured 2026-10-02). An UNTINTED last section is
   only spacing, so the end room replaces it; a tinted band keeps its own, or its colour would stop at the text. */
.re-main > .re-section:last-child:not(.re-section-muted,.re-section-card,.re-section-primary,.re-section-accent){padding-bottom:0}

/* ── re-shell — top-level split: sidebar | content column ─────────────────
   .re-shell is the flex row (full height, no page scroll). Inside: an
   .re-sidebar and an .re-shell-body (a flex-column holding a navbar + re-main). */
.re-shell{display:flex;height:100dvh;overflow:hidden}          /* dvh: 100vh sits under the iOS address bar */
.re-shell-body{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden;position:relative}  /* positioning context for FAB/overlays */

/* ── Mobile app shell (re-app + re-banner) — from _labs §3.2 ─────
   Wrap re-shell in re-app to (1) stack a full-width banner above the chrome and
   (2) let phones swap the sidebar for a bottom re-tabbar. Scoped to re-app so a
   bare re-shell (docs-style desktop layout) keeps its own responsive rules. */
.re-app{display:flex;flex-direction:column;height:100dvh;overflow:hidden}
.re-app > .re-shell,
.re-app > .re-shell-body{height:auto;flex:1 1 auto;min-height:0}   /* -body directly = topbar-only app (no sidebar) */
.re-app .re-sidebar{height:auto}   /* stretch to the shell (not 100dvh) so the collapse foot stays in view under a banner */
@media (max-width:1023px){
  .re-app .re-sidebar{display:none}                        /* nav moves to re-tabbar */
  .re-app .re-navbar{padding-top:env(safe-area-inset-top)}
  .re-app:has(> .re-banner) .re-navbar{padding-top:0}      /* banner already cleared the notch */
}
/* Full-width strip above the app chrome (offline / maintenance / preview).
   Solid re-c-{role}; takes the notch inset itself. flex:0 0 auto keeps footer/
   tabbar position unchanged whether the banner is present or not. */
.re-banner{position:relative;flex:0 0 auto;display:flex;align-items:center;justify-content:center;gap:var(--re-space-2);
  padding:calc(var(--re-space-2) + env(safe-area-inset-top)) calc(var(--re-space-8) + var(--re-space-2)) var(--re-space-2);
  font-size:calc(0.8125rem * var(--font-scale));font-weight:500;line-height:1.35;text-align:center;
  background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground))}
/* v7 §V7-11 (same class of bug as the sheet) — only the banner AT THE TOP OF THE SCREEN
   clears the notch. Stack two and the second one is already below the first, so its
   env() is dead space: measured with a 47px inset, both banners took padding-top:57px
   and the second one's text sat 47px lower than it should. The navbar case was already
   handled (.re-app:has(> .re-banner) .re-navbar), this is its sibling. */
.re-banner ~ .re-banner{padding-top:var(--re-space-2)}
.re-banner a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.re-banner p{margin:0}   /* a stray <p> inside a banner must not add margin */
/* Optional dismiss button, pinned right and vertically centred on the strip. */
.re-banner-close{position:absolute;top:50%;right:var(--re-space-3);transform:translateY(-50%);
  display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;
  background:none;border:none;border-radius:var(--radius-full);color:inherit;opacity:.75;cursor:pointer;transition:opacity .12s,background .12s}
.re-banner-close:hover{opacity:1;background:color-mix(in srgb,currentColor 18%,transparent)}

/* ── re-sidebar + re-nav ──────────────────────────────────────────────────
   Extracted verbatim from components/sidebar-nav.html — the icon/rail/guide
   geometry was hand-tuned so icons never jump on collapse. DO NOT retune
   these pixel values (icon 24, rail 56, guide 20/50, chrome pads 8/16, foot
   13/30) without re-verifying the collapse against the reference.

   Class map (old → canonical):
     .sidebar → .re-sidebar        .sidebar-brand → .re-sidebar-brand
     .sidebar-scroll → .re-sidebar-scroll   .sidebar-nav → .re-nav
     .nav-l1 → .re-nav-item        .nav-l2 → .re-nav-sub    .nav-l3 → .re-nav-leaf
     .icon-l1/.icon-l2 → .re-nav-icon   .label-l1/.label-l2 → .re-nav-label
     .section → .re-nav-group      .subsection → .re-nav-subgroup
     .nav-children-l2/l3 → .re-nav-children   .chevron → .re-nav-chevron
     .nav-badge → .re-nav-badge    .sidebar-foot → .re-sidebar-foot
     .foot-icon → .re-sidebar-toggle
   ─────────────────────────────────────────────────────────────────────── */
.re-sidebar{width:256px;height:100dvh;display:flex;flex-direction:column;background:var(--card);border-right:1px solid var(--border);overflow:visible}
.re-sidebar-brand{height:calc(56px * var(--density-mult,1));display:flex;align-items:center;padding:0 calc(16px * var(--density-mult,1));border-bottom:1px solid var(--border);flex-shrink:0}
.re-sidebar-brand-name{font-size:15px;font-weight:800;color:var(--foreground);white-space:nowrap}
.re-sidebar-scroll{flex:1;overflow-y:auto;overflow-x:hidden}
/* flex+gap → every top-level item (standalone or group) gets identical spacing.
   Horizontal padding 8 → tighter rail; item highlight keeps 8 inner padding, icons at 16 left. */
.re-nav{padding:calc(16px * var(--density-mult,1)) calc(8px * var(--density-mult,1));display:flex;flex-direction:column;gap:2px}

/* ── L1 item ── */
.re-nav-item{
  display:flex;align-items:center;
  min-height:calc(32px * var(--density-mult,1));
  padding:calc(6px * var(--density-mult,1)) calc(8px * var(--density-mult,1));
  border-radius:var(--radius-inset);cursor:pointer;
  color:var(--foreground);text-decoration:none;width:100%;
  background:none;border:none;font-family:inherit;
}
/* Hover is the brand's tint, the active row's colour at half strength: a grey hover in a violet menu read as a
   different colour from the logo above it (Ersin, 2026-10-02: "menü hover/active color logo ile aynı olmalı"). */
.re-nav-item:hover{background:color-mix(in oklch, var(--primary) 6%, transparent)}
.re-nav-item.active{background:color-mix(in oklch, var(--primary) 12%, transparent);color:var(--primary)}

/* Icon container — L1/brand 24px, L2 16px; 10px right margin */
.re-nav-icon{--re-nav-icon-slot:calc(24px * var(--density-mult,1));--re-nav-icon:calc(var(--re-nav-icon-slot) * .667);
  display:flex;align-items:center;justify-content:center;width:var(--re-nav-icon-slot);flex-shrink:0;margin-right:calc(10px * var(--density-mult,1))}
/* The rail's icons are a COLUMN: they align with each other down the rail, not with each
   row's own type — a group heading is 11px uppercase and its children are 13px, so sizing
   by `em` would have drawn a different width per row. The slot decides, once, and density
   carries it. Markup had four widths for this one slot (14 / 16 / 18 / 20).
   Row-scoped on purpose: re-sidebar-brand puts a BRAND MARK in the same class, and a logo
   is not an icon. */
:is(.re-nav-item,.re-nav-sub,.re-nav-leaf) > .re-nav-icon > svg{width:var(--re-nav-icon);height:var(--re-nav-icon)}
.re-nav-sub > .re-nav-icon{width:calc(16px * var(--density-mult,1))}
/* An icon the app does not carry (a sibling's page in the shell's menu names an icon from ITS own
   set) still takes the slot, so the labels stay on one x: a dot, drawn here, needing no asset. An
   empty slot read as a broken icon (contacts O3, 2026-10-01). */
.re-nav-dot{width:4px;height:4px;border-radius:50%;background:currentColor;opacity:.45}

/* Labels — L1 uppercase bold 11px, L2 13px medium */
.re-nav-item > .re-nav-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;flex:1;text-align:left;white-space:nowrap}
.re-nav-sub  > .re-nav-label{font-size:13px;font-weight:500;flex:1;text-align:left;white-space:nowrap}

/* Chevron */
.re-nav-chevron{width:14px;height:14px;flex-shrink:0;color:var(--muted-foreground);transition:transform .15s}
/* A heading's one action (reNavHtml `action`), before the chevron, shown on the heading's hover or focus and always on
   touch. The label leaves room for it so the text never runs under it; the chevron stays where every other one is. */
.re-nav-action{--re-nav-action-icon:calc(22px * .64);position:absolute;right:calc(8px * var(--density-mult,1) + 18px);width:22px;height:22px;display:inline-flex;
  align-items:center;justify-content:center;border-radius:var(--radius-inset);color:var(--muted-foreground);opacity:0;
  transition:opacity .12s,background .12s}
.re-nav-subgroup > .re-nav-action{top:calc((30px * var(--density-mult,1) - 22px) / 2)}
.re-nav-group > .re-nav-action{top:calc((32px * var(--density-mult,1) - 22px) / 2)}
.re-nav-action > svg{width:var(--re-nav-action-icon);height:var(--re-nav-action-icon)}
:is(.re-nav-sub,.re-nav-item):hover + .re-nav-action,.re-nav-action:hover,.re-nav-action:focus-visible,
:is(.re-nav-sub,.re-nav-item):focus-visible + .re-nav-action{opacity:1}
.re-nav-action:hover{background:color-mix(in oklch, var(--primary) 12%, transparent);color:var(--primary)}
:is(.re-nav-sub,.re-nav-item):has(+ .re-nav-action) > .re-nav-label{margin-right:26px}
@media (hover:none){.re-nav-action{opacity:1}}
.re-nav-group.open > .re-nav-item .re-nav-chevron,
.re-nav-subgroup.open > .re-nav-sub .re-nav-chevron{transform:rotate(90deg)}

/* ── Group (L1 section) ── */
.re-nav-group{position:relative}
.re-nav-children{display:none;margin-top:2px}
/* Children stack with the L1 gap (2px): with display:block two adjacent highlighted rows (active + hover) fused into one
   block one level down while L1 showed the seam (Ersin, 2026-10-02). */
.re-nav-group.open > .re-nav-children{display:flex;flex-direction:column;gap:2px}
/* Vertical guide line — left aligned to L1 icon center */
.re-nav-group.open::before{
  content:'';position:absolute;
  left:calc(20px * var(--density-mult,1));
  top:calc(34px * var(--density-mult,1));bottom:0;width:1px;background:var(--border);
}

/* ── L2 item (re-nav-sub) ── */
.re-nav-sub{
  display:flex;align-items:center;
  min-height:calc(30px * var(--density-mult,1));
  padding:calc(5px * var(--density-mult,1)) calc(8px * var(--density-mult,1)) calc(5px * var(--density-mult,1)) calc(42px * var(--density-mult,1));
  margin-left:0;border-radius:var(--radius-inset);cursor:pointer;
  color:var(--muted-foreground);text-decoration:none;width:100%;
  background:none;border:none;font-family:inherit;
}
.re-nav-sub:hover{background:color-mix(in oklch, var(--primary) 6%, transparent)}
.re-nav-sub.active{background:color-mix(in oklch, var(--primary) 12%, transparent);color:var(--primary)}

/* ── Sub-group (L2 collapsible) ── */
.re-nav-subgroup{position:relative}
.re-nav-subgroup.open > .re-nav-children{display:flex;flex-direction:column;gap:2px}
.re-nav-subgroup.open::before{
  content:'';position:absolute;
  left:calc(50px * var(--density-mult,1));
  top:calc(32px * var(--density-mult,1));bottom:0;width:1px;background:color-mix(in oklch, var(--border) 70%, transparent);
}

/* ── L3 leaf — no icon, indented under L2 icon ── */
.re-nav-leaf{
  display:flex;align-items:center;white-space:nowrap;
  padding:calc(5px * var(--density-mult,1)) calc(8px * var(--density-mult,1)) calc(5px * var(--density-mult,1)) calc(68px * var(--density-mult,1));
  margin-left:0;border-radius:var(--radius-inset);cursor:pointer;
  color:var(--muted-foreground);text-decoration:none;width:100%;
  font-size:12.5px;font-weight:400;
}
.re-nav-leaf:hover{background:color-mix(in oklch, var(--primary) 6%, transparent)}
.re-nav-leaf.active{background:color-mix(in oklch, var(--primary) 12%, transparent);color:var(--primary)}

/* Nav mini-badge (e.g. "soon") */
.re-nav-badge{
  font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;
  padding:1px 5px;border-radius:calc(var(--radius-inset) - 2px);margin-left:4px;flex-shrink:0;
  background:color-mix(in oklch, var(--primary) 15%, transparent);color:var(--primary);
}

/* ── Sidebar footer — collapse toggle, left-aligned ── */
/* padding-left 13 → toggle glyph lands on the 28px axis (13 + 30/2 = 28), same as nav icon glyphs.
   Uniform in open + rail (13+30+13=56) → the icon never jumps horizontally on collapse. */
.re-sidebar-foot{height:calc(44px * var(--density-mult,1));flex-shrink:0;border-top:1px solid var(--border);display:flex;align-items:center;padding:0 calc(13px * var(--density-mult,1))}
/* The DS never sized the icon this button hosts, so the size lived in the markup — and an svg
   with no width in a centred flex slot renders at the SLOT's size. Measured: 30x30, filling
   the whole button, against the 16px every documented example writes by hand. Same shape as
   the 180px chevron in re-row-trailing, one control over. */
.re-sidebar-toggle{--re-sidebar-toggle-h:calc(30px * var(--density-mult,1));
  --re-sidebar-toggle-icon:calc(var(--re-sidebar-toggle-h) * .53);
  width:var(--re-sidebar-toggle-h);height:var(--re-sidebar-toggle-h);border-radius:var(--radius-inset);border:none;background:transparent;cursor:pointer;color:var(--muted-foreground);display:flex;align-items:center;justify-content:center}
.re-sidebar-toggle > svg{width:var(--re-sidebar-toggle-icon);height:var(--re-sidebar-toggle-icon)}
.re-sidebar-toggle:hover{color:var(--foreground);background:color-mix(in oklch, var(--foreground) 7%, transparent)}

/* ── Collapse → icon rail (icons stay LEFT-aligned & in place; only labels hide → no jump) ── */
.re-sidebar{transition:width .18s ease}
/* Rail width = 8 nav-pad + 8 item-pad + 24 icon + 8 item-pad + 8 nav-pad + the 1px border-right
   → icon keeps EXACTLY its open-state left offset (16px) and gets an equal 16px on the right.
   The border was missing from the sum until 2026-09-30: the content box was 55px, so every rail
   highlight sat 0.5px left of its icon (bg centre 27.5, icon 28) — Ersin saw the lean. */
.re-sidebar-rail{width:calc(56px * var(--density-mult,1) + 1px)}
.re-sidebar-rail:has(.re-nav:hover){width:256px}   /* peek on hover */
/* Collapsed: hide labels/badges/chevrons/children ONLY. Paddings untouched → icon left (16) and
   hover-bg left edge (8) identical to open state; only the bg's right edge pulls in. Icons never hop. */
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-sidebar-brand-name,
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-label,
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-badge,
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-chevron{display:none}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-action{display:none}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-children{display:none}
/* Drop the icon→label gap so the icon sits symmetrically (8px) inside its highlight box */
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-item > .re-nav-icon{margin-right:0}
/* A group whose active child is now hidden lights up its parent header instead */
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-group:has(.active) > .re-nav-item{background:color-mix(in oklch, var(--primary) 12%, transparent);color:var(--primary)}

/* ══ App shell — one shell for the family (O2, 2026-09-30) ══════════════════════════════
   Every app is the dashboard layout: a sidebar whose head is the BRAND and whose menu is the
   app's own pages (what re-page-tabs held), a navbar with the breadcrumb and the account menu,
   and a fixed footer bar. Four parts are new; the rest is re-sidebar / re-nav as they were.

   ONE AXIS. re-nav's 8px + re-nav-item's 8px + half the 24px icon slot put every row's icon
   centre at 28px × density. Anything in the column — the brand's mark, an app mark, the group
   guide line — sits on that x because it sits in a re-nav-icon slot, never because a padding
   was tuned to match. The first draft of this shell tuned a padding and missed by 4px.
   Measured on layouts/app-shell.html: brand and every row, max deviation 0.03px, at 1280/390 ×
   compact/standard/comfortable. */

/* re-app-mark — an app's glyph on its brand tint: re-gate-mark's recipe (brand 14% over card,
   brand glyph, brand 28% edge) at the nav slot's size, so a product reads the same in the menu
   as on its own gate. data-brand on the mark (or an ancestor) redefines --brand.
   -lg is the brand row's (28px); it is wider than the 24px slot and the slot centres it, so the
   centre stays on the axis without a second number. -neutral is for a row that is not an app
   (Home). */
.re-app-mark{--_s:calc(24px * var(--density-mult,1));width:var(--_s);height:var(--_s);flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center;border-radius:calc(var(--radius-md) * .6);
  background:color-mix(in srgb,var(--brand) 14%,var(--card));color:var(--brand);
  border:1px solid color-mix(in oklch,var(--brand) 28%,transparent)}
.re-app-mark > svg{width:58%;height:58%;flex-shrink:0}
.re-app-mark-lg{--_s:calc(28px * var(--density-mult,1))}
.re-app-mark-initial{font-size:calc(var(--_s) * .5);font-weight:700;line-height:1}
.re-app-mark-neutral{--brand:var(--muted-foreground)}
/* A row that leads with a mark carries 24px of tinted tile against 11px caps: 4px more air, and
   the open group's guide line starts under the taller row (it is placed from the row's top). */
.re-nav-item:has(> .re-nav-icon > .re-app-mark){min-height:calc(36px * var(--density-mult,1))}
.re-nav-group.open:has(> .re-nav-item > .re-nav-icon > .re-app-mark)::before{top:calc(38px * var(--density-mult,1))}

/* re-nav-section — a heading between groups of rows. Not a re-nav-item: that label is the L1
   heading of a COLLAPSIBLE group and it is a button. Same letter-spacing as the L1 label so two
   uppercase lines read as one family, one step smaller and muted so a heading never reads as a
   destination. Left edge = the icon slot's left edge (16px × density).
   In the collapsed rail the words go and the gap stays, so the rows under it do not climb. */
.re-nav-section{padding:calc(16px * var(--density-mult,1)) calc(8px * var(--density-mult,1)) calc(6px * var(--density-mult,1));
  font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-foreground);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-nav-section{color:transparent}

/* re-brand-switcher — the sidebar head as a picker: the current brand's mark and name, a
   chevron that says it opens, and a re-dropdown listing the family's brands. Choosing one is a
   NAVIGATION (another domain), not a filter; the shell does not change in place.
   It is a re-dropdown: toggleDropdown opens it, the document click closes it, Escape closes it.
   The one behaviour it adds is the search filter (redev-ui.js, delegated).
   The button holds the MARK too (re-nav-icon slot, name, chevron): the hover covers the logo and
   the menu opens under the button's left edge — Ersin's pick over a button that started at the
   name (2026-09-30). The axis holds: the button's negative margin is its padding + its 1px border,
   so its slot starts where the brand row's 16px padding puts every other slot. Measured first
   without the border in the sum: the mark sat 1.00px right of the rows. */
/* CONCENTRIC (Ersin, 2026-09-30: "iç içe çember gibi, aynı yayı çizmeliler"). One inset --_i
   ties three arcs to the mark's: the button's corner is the mark's radius + the gap between them,
   the menu opens with the button's radius, and the search field inside it sits the same gap in
   with the mark's radius again. The button pads the mark by --_i on every side (the lg mark is
   2px × density wider than its 24px slot on each side, --_over, so the horizontal padding adds it),
   and bleeds out by the same amount on BOTH sides — so the hover is centred on the mark in the
   open column and in the rail alike. First version bled left only: in the rail the hover sat 5px
   left of the mark (bg centre 23, mark 28). */
.re-sidebar-brand > .re-brand-switcher{flex:1;min-width:0;display:flex;
  --_i:calc(4px * var(--density-mult,1));--_over:calc(2px * var(--density-mult,1));
  --_mr:calc(var(--radius-md) * .6);--_R:calc(var(--_mr) + var(--_i) + 1px);
  --_bleed:calc(var(--_i) + var(--_over) + 1px)}
.re-brand-btn{flex:1;min-width:0;display:flex;align-items:center;gap:0;position:relative;
  margin-inline:calc(-1 * var(--_bleed));padding:var(--_i) calc(var(--_i) + var(--_over));
  border:1px solid transparent;border-radius:var(--_R);background:none;font:inherit;color:var(--foreground);cursor:pointer;text-align:left}
.re-brand-btn:hover,.re-brand-switcher:has(.re-dropdown-menu.open) .re-brand-btn{background:color-mix(in oklch,var(--foreground) 6%,transparent)}
.re-brand-btn > .re-sidebar-brand-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-brand-btn > .re-brand-chevron{width:14px;height:14px;flex-shrink:0;color:var(--muted-foreground)}
/* Under the button's left edge, with its radius and its WIDTH — the menu never runs past the sidebar
   (Ersin, 2026-09-30; it was 300px and crossed the sidebar's edge). In the collapsed rail the button is
   only the mark, so the menu takes the open column's width instead. */
.re-brand-menu{left:calc(-1 * var(--_bleed));width:calc(100% + 2 * var(--_bleed));min-width:0;max-width:none;
  border-radius:var(--_R);padding:0;overflow:hidden}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-brand-menu{width:calc(255px - 32px * var(--density-mult,1) + 2 * var(--_bleed))}
/* The field keeps the menu's roomier 10px inset (Ersin preferred it to the mark's 4px) and follows the
   menu's arc: its radius is the menu's minus that inset and the border. At the default radius that is
   1.2px — a square corner — so it is floored at --radius-inset (6px): concentric where the arithmetic
   allows it (data-radius="full": 8.4px, exact), and never sharper than the DS's inner-part radius. */
.re-brand-menu-search{padding:var(--re-space-2);border-bottom:1px solid var(--border)}
.re-brand-menu-search > .re-search-row{border-radius:max(calc(var(--_R) - var(--re-space-2) - 1px),var(--radius-inset))}
/* The magnifier sits on the brand marks' axis below it, and the field's type is the names' size
   (Ersin, 2026-09-30). Item: re-dropdown-item's --re-space-3 padding + half the 24px mark. Field:
   the 10px inset + its 1px border + this padding + half the 15px icon. Solve for the padding. The
   input was a fixed 13px while the names follow density — equal only at standard. */
.re-brand-menu-search > .re-search-row{padding-left:calc(var(--re-space-3) + 12px * var(--density-mult,1) - var(--re-space-2) - 1px - 7.5px)}
.re-brand-menu-search .re-search-input{font-size:calc(0.8125rem * var(--font-scale))}
.re-brand-menu-list{max-height:min(420px,60dvh);overflow-y:auto;padding:var(--re-space-1) 0}
.re-brand-menu-label{padding:var(--re-space-2) var(--re-space-3) var(--re-space-1);
  font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-foreground)}
.re-brand-menu .re-dropdown-item > .re-brand-menu-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-brand-menu .re-dropdown-item[aria-current="true"]{font-weight:600}
.re-brand-menu-empty{padding:var(--re-space-3);font-size:calc(.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-brand-chevron{display:none}
.re-sidebar-rail:not(:has(.re-nav:hover)) .re-brand-btn > .re-nav-icon{margin-right:0}

/* A page header OUTSIDE re-main (the dashboard shape) and a centred column inside it: re-main
   reserves the scrollbar gutter on both edges (S12-14, `stable both-edges`) and the header, not a
   scroller, reserves nothing — so wherever the column is narrower than its max-width the title
   sat 4px left of the first row (measured at 1000/1200px with a 4px classic bar: 283 vs 287).
   The header takes the MEASURED gutter initMainGutter() publishes on re-shell-body, so it matches
   whatever the platform draws: 4px with a classic bar, 0 with an overlay one. No JS → 0, the
   pre-shell behaviour. */
.re-shell-body > .re-page-header > .re-ph-body:has(> .container:not(.container-full)),
.re-shell-body > .re-page-header > .re-page-tabs:has(> .container:not(.container-full)){
  padding-left:var(--re-main-gutter-l,0px);padding-right:var(--re-main-gutter-r,0px)}   /* the tab strip too: measured 283 vs the title's 287 */

/* The footer bar is re-footer (below), unchanged: pinned after re-main, the same 44px and rule
   as re-sidebar-foot, so the two read as ONE line across the shell. What the shell adds is its
   relationship to the colophon:
   - a re-colophon INSIDE re-footer is the bar's left side, its content margin taken off. In a
     shell app the in-page colophon of S12-6 (end of Hesabım / Ayarlar) is then not written on a
     desktop — the bar says it on every screen;
   - on a phone (≤860px, the sidebar is a drawer) the bar goes: 44px of fixed chrome is a line
     of content there. The in-page colophon comes back as `re-colophon re-phone-only`.
   One fact, two places, never both on one screen. */
.re-footer > .re-colophon{margin:0;text-align:left}
@media (max-width:860px){
  .re-shell:has(> .re-sidebar-drawer) > .re-shell-body > .re-footer{display:none}
  /* In the drawer the rail toggle has nothing to collapse: the drawer IS the collapsed state. */
  .re-sidebar-drawer .re-sidebar-toggle{display:none}
}

/* ── re-drawer — slide-in panel (mobile nav, filters, detail drawer) ───
   Fixed panel parked off-screen; add .open to slide in. Pair with an
   .re-drawer-backdrop (data-for="<panel id>") for the dim + click-to-close.
   Drive with the toggleDrawer(id) helper (see preview → Drawer). */
.re-drawer{
  position:fixed;top:0;bottom:0;
  width:min(300px,86vw);
  background:var(--card);
  z-index:410;
  display:flex;flex-direction:column;overflow-y:auto;
  box-shadow:var(--shadow-lg);
  transition:transform .22s ease;
}
.re-drawer-left {left:0;  transform:translateX(-100%);border-right:1px solid var(--border)}
.re-drawer-right{right:0; transform:translateX(100%); border-left:1px solid var(--border)}
.re-drawer.open{transform:translateX(0)}
.re-drawer-sm{width:min(240px,80vw)}
.re-drawer-lg{width:min(400px,92vw)}

.re-drawer-backdrop{
  position:fixed;inset:0;
  background:color-mix(in oklch, var(--foreground) 30%, transparent);
  z-index:400;
  opacity:0;visibility:hidden;
  transition:opacity .22s ease, visibility .22s ease;
}
.re-drawer-backdrop.open{opacity:1;visibility:visible}

/* Optional header row with a close button */
.re-drawer-header{display:flex;align-items:center;justify-content:space-between;height:56px;padding:0 16px;border-bottom:1px solid var(--border);flex-shrink:0}
.re-drawer-close{width:30px;height:30px;border-radius:var(--radius-inset);border:none;background:transparent;cursor:pointer;color:var(--muted-foreground);display:flex;align-items:center;justify-content:center}
.re-drawer-close:hover{color:var(--foreground);background:color-mix(in oklch, var(--foreground) 7%, transparent)}

/* Hamburger trigger — icon button matching the navbar-item hit area */
.re-navbar-toggle{--re-navbar-toggle-size:var(--re-navbar-control-h,var(--control-h-sm));--re-navbar-toggle-icon:18px;
  display:inline-flex;align-items:center;justify-content:center;
  width:var(--re-navbar-toggle-size);height:var(--re-navbar-toggle-size);
  border-radius:var(--radius-control);border:none;background:transparent;cursor:pointer;color:var(--foreground);flex-shrink:0}
.re-navbar-toggle > svg{width:var(--re-navbar-toggle-icon);height:var(--re-navbar-toggle-icon)}
/* ── Optical alignment at the bar's ends ──────────────────────────────────────
   An icon button's hit area is bigger than its glyph, so a 34px button flush to the gutter
   puts its 18px icon 8px further in — and the icon is what the eye lines up with. Measured
   on docs-toc at 390px: the button box started at the gutter (24px) and the hamburger's
   strokes at 32px, against a heading at 24px.

   The fix is a negative margin, not less padding: zeroing the inset would drag the touch
   target under the floor Touch & Mobile sets. The box overhangs the gutter by its own
   inset, the glyph lands on it, and the hover fill stays centred on the icon.

   Both numbers are the DS's, declared right above and applied to the svg too, so the inset
   is EXACT — not a magic 8px that happens to match today's markup. A call site with a
   different icon size sets --re-navbar-toggle-icon and the alignment follows. */
.re-navbar > .re-navbar-toggle:first-child,
.re-navbar-left > .re-navbar-toggle:first-child{
  margin-left:calc(-1 * (var(--re-navbar-toggle-size) - var(--re-navbar-toggle-icon)) / 2)}
.re-navbar > .re-navbar-toggle:last-child,
.re-navbar-right > .re-navbar-toggle:last-child{
  margin-right:calc(-1 * (var(--re-navbar-toggle-size) - var(--re-navbar-toggle-icon)) / 2)}
.re-navbar-toggle:hover{background:color-mix(in oklch, var(--foreground) 6%, transparent)}
/* An avatar in the bar is one of its controls, so it takes the bar's height too — not the
   36px it uses in a list, and not a third number of its own. */
.re-navbar .re-avatar{width:var(--re-navbar-control-h);height:var(--re-navbar-control-h)}

/* ── re-sidebar-drawer — the sidebar IS the drawer on a phone ──────────────────
   Measured 2026-09-02 at 390px: docs-toc, docs-sidenav, dashboard and dash-tabs all kept a
   256px sidebar — 66% of the screen — leaving the article 134px. Not a missing demo, a
   defect in shipped markup. The rule that solves it existed but was locked to app shells:
   `.re-app .re-sidebar{display:none}` only fires inside re-app, and these four use a bare
   re-shell.

   `display:none` is not the fix here either. An app moves its nav to a tab bar, so hiding
   the sidebar loses nothing; a docs shell has no tab bar, so hiding it would leave the page
   with NO navigation at all. The nav has to go somewhere, and the somewhere is a drawer.

   One element, two presentations — the sidebar becomes the drawer rather than a second copy
   of the nav being kept in sync beside it. Duplicated nav markup is the obvious route (the
   docs site itself takes it, because Astro makes the nav a component and there is no drift
   risk); in a hand-copied HTML skeleton that same duplication is 26 links to keep in step.

   Pair with `re-sidebar-drawer-btn` on the trigger and an `re-drawer-backdrop[data-for]`,
   and drive both with the same `toggleDrawer(id)` helper as a real drawer.

   860px because that is the breakpoint the docs site has run on; below it the sidebar is
   taking a third of the screen. It is a literal — a media query cannot read a token.

   ⚠ Declared AFTER .re-navbar-toggle on purpose. The trigger carries both classes and both
   set `display` at one class of specificity, so source order decides — with this block
   above, .re-navbar-toggle{display:inline-flex} won and the hamburger showed at 1280px
   next to a sidebar that was already there. Measured, not reasoned. */
.re-sidebar-drawer-btn{display:none}
.re-sidebar-search{display:none;padding:var(--re-space-2) var(--re-space-2) var(--re-space-1)}
@media (max-width:860px){
  .re-sidebar-drawer{
    position:fixed;top:0;bottom:0;left:0;
    width:min(300px,86vw);
    z-index:410;
    transform:translateX(-100%);
    transition:transform .22s ease,box-shadow .22s ease;
    border-right:1px solid var(--border);
  }
  /* The shadow belongs to the OPEN drawer. Parked at translateX(-100%) its blur still reached
     ~40px past the right edge, a grey strip down the left of every phone page (390px,
     dashboard + app-shell, 2026-09-30). */
  .re-sidebar-drawer.open{transform:translateX(0);box-shadow:var(--shadow-lg)}
  .re-sidebar-drawer-btn{display:inline-flex}
  /* ── The top bar rearranges in the same breath ────────────────────────────
     A phone top bar holds a hamburger, a breadcrumb and a right-hand cluster, and the
     cluster wins: measured on docs-toc at 390px, search + bell + a labelled "Ask AI" +
     avatar left the breadcrumb squeezed. So the cluster gives ground, in the order that
     costs least:
       · a label goes, the icon stays          → re-phone-hide on the <span>
       · a duplicated affordance goes entirely → re-phone-hide on the button, and the
         action moves somewhere it still fits (search → the drawer, below)
       · what is left keeps its full hit area
     Same breakpoint as the drawer, and that is the point: this is ONE shell state, not
     three independent responsive decisions. */
  .re-phone-hide{display:none}
  .re-sidebar-search{display:block}
  /* A labelled button keeps its label padding when the label goes, and the icon then sits in
     an over-wide box — measured, "Ask AI" collapsed to 40x32 next to a 32px avatar (the navbar's own
     --re-navbar-control-h at standard, NOT an avatar size class — .re-navbar .re-avatar reads
     that token, so it is 25.6 / 32 / 34.4 across the densities and never one of 20/28/36/48). Squaring
     it needs no new class: `:has(> .re-phone-hide)` IS the condition. aspect-ratio rather
     than a width, so it follows whatever height the button already has. */
  .re-btn:has(> .re-phone-hide){padding:0;gap:0;aspect-ratio:1}
}
/* The two halves of the pair. `re-phone-only` is written as a hide-above rule rather than a
   show-below one so the element keeps whatever `display` its own class gives it — setting
   `display:block` here would flatten a flex row, and `revert`/`unset` resolve to `inline`.
   Same shape as re-visible-md-up in utilities.css.
   ⚠ These share re-sidebar-drawer's 860px, NOT the 768px of re-hidden-md-down. The two are
   different questions — "is the shell in phone form" vs "does this content still fit two
   columns" — and the DS currently has three thresholds (768 content · 860 docs shell ·
   1023 app shell). Worth consolidating; not silently. */
@media (min-width:861px){ .re-phone-only{display:none} }

/* A search slot in the sidebar, between the brand and the nav — where search goes when the
   top bar can no longer hold it. Hidden by default and revealed in phone form, because a
   shell that keeps its top-bar search would otherwise show two. Set `display:block` on it
   to keep a permanent sidebar search. */

/* ── Keyboard focus ring (a11y) ──────────────────────────────────────────
   Unified :focus-visible outline for every interactive component. Shows only
   for keyboard navigation (not mouse clicks) and re-tints with data-brand via
   --c-focus. Switch keeps its own ring on the track (input is visually hidden). */
.re-btn:focus-visible, .re-input:focus-visible, .re-textarea:focus-visible, .re-select:focus-visible,
.re-checkbox:focus-visible, .re-radio:focus-visible, .re-nav-item:focus-visible, .re-nav-sub:focus-visible,
.re-nav-leaf:focus-visible, .re-dropdown-item:focus-visible, .re-tab:focus-visible, .re-page:focus-visible,
.re-link:focus-visible, .re-ac-trigger:focus-visible, .re-navbar-toggle:focus-visible, .re-sidebar-toggle:focus-visible, .re-segment-item:focus-visible, .re-tree-node:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

/* ── Smooth the hover bg change on nav / menu / tab items (were instant) ── */
.re-nav-item, .re-nav-sub, .re-nav-leaf, .re-dropdown-item, .re-tab, .re-ac-trigger {
  transition: background .12s, color .12s;
}

/* ── Disabled state — form controls (btn/checkbox/radio/switch/page already set) ── */
.re-input:disabled, .re-textarea:disabled, .re-select:disabled {
  opacity: .55; cursor: not-allowed; background: var(--muted);
}
.re-dropdown-item:disabled, .re-dropdown-item[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; pointer-events: none;
}

/* ══ Page header + underline tab bar (re-page-header / re-page-tabs) ══
   Sticky page heading with glass-blur on scroll + an underline tab bar. Add
   .scrolled (JS, on scroll) for the blur + a bottom border (default); a contained
   re-page-tabs removes the border via :has(). For the full-width variant put a
   .container inside. */
/* The bottom border is there AT REST, not only on scroll. It used to be
   `1px solid transparent` until `.scrolled` gave it a colour, which left a tabless header
   with no boundary at all while a tabbed one had the strip's line the whole time — the same
   screen answering the same question two ways depending on whether it happened to have tabs.
   Measured at 390px at rest: tabless header `1px` of `rgba(0,0,0,0)`, tabbed header width 0
   with the strip drawing the line. Reported twice, in two products, and four consumers had
   written the identical override (`plan` re-ext.css:204, `split` :188, `calendar`, `tasks`):
       .re-page-header:not(:has(.re-page-tabs)){border-bottom-color:var(--border)}
   ⚠ `re-ph-notabs` could not have done it — `git log -S` says that class only ever worked
   through `.scrolled` and never drew a resting line; it stays a no-op.
   The default is the border itself rather than a `:not(:has())` rule, because the tabbed case
   already zeroes the WIDTH below (§V18-1) and two rules for one boundary is how a default
   drifts. `border-bottom-color` also leaves the transition list: it no longer changes, and a
   transition on a property that never moves is dead code. (labs §V23-1) */
/* ── A page header that OVERLAYS the scroller ──────────────────────────────────────────
   The two things a page header is asked for are in direct conflict while it lives INSIDE
   re-main: content passing under it (which is what backdrop-filter needs) and a bottom rule
   that reaches the shell's edge. A scroller never lets a child paint over its own scrollbar —
   measured at 8x on three gutter settings (auto / stable / both-edges), the rule stops at the
   scrollbar's inner edge in all three and no bleed value reaches further.

   Outside the scroller the rule reaches and the blur has nothing to blur. So: OUTSIDE the
   scroller, positioned OVER it. The header is a direct child of re-shell-body, absolutely
   placed across the top; re-main takes a top padding equal to its height, so the first screen
   starts below it while everything scrolls under it. Both halves, no trade.

   `>` here is a contract, not an assumption: this rule is defined by the header being a direct
   child of re-shell-body — that IS the pattern. (The same `>` inside `:has()` has been wrong
   three times in this file when it was used to find a component that could be anywhere.)
   --re-ph-h and --re-ph-top are published by initScrollHeaders(); with no JS both are 0 and
   the layout degrades to the header sitting on top of the content rather than above it, which
   is why the fallbacks are written and not left to `initial`.

   ⚠ --re-ph-top is NOT 0, and assuming it was put the header over the topbar in the two
   dashboard layouts — `top:0` is the top of re-shell-body, and re-shell-body starts at the
   NAVBAR, not at the scroller. The header has to start where re-main starts, which is a
   measured number for the same reason the height is: the navbar is 56px today and its content
   (brand, breadcrumbs, search, avatar) moves with density and with a wrapped line. */
/* ⚠ `:not(.re-page-header-collapse)` — the overlay and the COLLAPSE variant are two different
   shells, not two placements of one header, and a consumer reached for both at once.

   Collapse is defined by re-navbar moving INSIDE re-main so it scrolls away, and the heading
   taking the space it leaves; the header is in flow there and its own position:sticky does the
   work. Overlay is the opposite arrangement — navbar outside the scroller, header out of flow
   over it — so a collapse header placed that way has no navbar to collapse against.

   And the mechanism does not merely lose its reason, it fights itself. Overlay reserves space
   by publishing the header's measured height as re-main's padding-top; a collapse header
   changes height continuously while you scroll. Measured at 390px, driving --re-ph-t from 0 to
   1 with that publish wired up:

       --re-ph-t   0     0.25   0.5    0.75   1
       header      89px  82     76     59     42
       re-main pad 89px  82     76     59     42
       first row   89px  82     76     59     42     ← 47px of content sliding UP mid-scroll

   That is the same shrinkage `overflow-anchor:none` exists to stop, coming back through a
   different door — and the anchor rule cannot touch it, because this is padding, not layout
   above an anchor node.

   So the guard is on the CSS, not just in prose: a collapse header put in the overlay slot
   stays in normal flow, which is degraded but coherent, instead of rendering as an overlay
   whose JS never runs. page-header.mdx states the contract.
   ⚠ Superseded for the WIRED case (KH1, 2026-10-02): the slide came from publishing the live height. The rule
   after this one overlays a collapse header once the script has wired it and publishes its OPEN height. */
.re-shell-body:has(> .re-main) > .re-page-header:not(.re-page-header-collapse){
  position:absolute;left:0;right:0;top:var(--re-ph-top,0px);z-index:20}
.re-shell-body:has(> .re-page-header:not(.re-page-header-collapse)) > .re-main{
  padding-top:var(--re-ph-h,0px);scroll-padding-top:var(--re-ph-h,0px)}
/* KH1 (2026-10-02): the guard above stays for the no-JS case, and the collapse gets the overlay only once
   initCollapseHeaders() has wired it (data-re-collapse="1"), because only then is --re-ph-h the OPEN height, held
   constant while the header shrinks over the content: the 47px slide came from publishing the live height. */
.re-shell-body:has(> .re-main) > .re-page-header-collapse[data-re-collapse="1"]{
  position:absolute;left:0;right:0;top:var(--re-ph-top,0px);z-index:20;
  /* Phase 2 (Ersin, 2026-10-02): once the title is at the navbar's size it stops shrinking and slides UP under the
     navbar. translateY lifts it, and the matching top inset clips whatever crossed its starting line, so it
     disappears at the navbar's bottom edge without painting over the bar (the header is taller than the bar at 390). */
  transform:translateY(calc(-1 * var(--re-ph-lift,0px)))}
/* The clip only while the header is lifting: at rest it must not exist, or the header's own menus (a detail page's
   Edit / Delete dropdown) are cut at its bottom edge (v46, calendar: menu bottom 216, header bottom 161). */
.re-shell-body:has(> .re-main) > .re-page-header-collapse[data-re-ph-lifting]{clip-path:inset(var(--re-ph-lift,0px) 0 0 0)}
.re-shell-body:has(> .re-page-header-collapse[data-re-collapse="1"]) > .re-main{
  padding-top:var(--re-ph-h,0px);scroll-padding-top:var(--re-ph-h,0px)}
.re-shell-body.re-ph-docked > .re-page-header-collapse{pointer-events:none}
/* Docked: the breadcrumb fades out sliding up, the page title (a button: tap = back to top) fades in sliding up from
   below, over the breadcrumb's box. Title size = the collapsed heading's (1rem), so the hand-over reads as one title. */
.re-shell-body:has(> .re-page-header-collapse[data-re-collapse="1"]) > .re-navbar{position:relative}
.re-shell-body:has(> .re-page-header-collapse[data-re-collapse="1"]) > .re-navbar .re-breadcrumb{transition:opacity .25s ease,transform .25s ease}
.re-shell-body.re-ph-docked > .re-navbar .re-breadcrumb{opacity:0;transform:translateY(-10px);pointer-events:none}
.re-navbar-title{position:absolute;top:0;bottom:0;display:flex;align-items:center;min-width:0;padding:0;border:0;background:none;
  font:inherit;font-size:calc(1rem * var(--font-scale));font-weight:700;color:var(--foreground);text-align:left;cursor:pointer;
  opacity:0;transform:translateY(10px);pointer-events:none;transition:opacity .25s ease,transform .25s ease}
.re-navbar-title > span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.re-shell-body.re-ph-docked > .re-navbar .re-navbar-title{opacity:1;transform:none;pointer-events:auto}
@media (prefers-reduced-motion:reduce){.re-navbar-title,.re-shell-body > .re-navbar .re-breadcrumb{transition:none}}
/* ⚠ `:has(> .re-main)` on the first rule is the guard, and it was missing on the first pass:
   docs-sidenav also puts its header directly in re-shell-body, but its re-main sits one level
   deeper (next to a second sidebar). There the header is ordinary flow content above a row,
   and overlaying it would have dropped it on top of that row. Overlay only when the scroller
   IS the sibling — which is the only case where "over the scroller" means anything. */

.re-page-header{position:sticky;top:0;z-index:20;background:var(--background);border-bottom:1px solid var(--border);transition:backdrop-filter .15s,background .15s}
.re-page-header.scrolled{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:color-mix(in oklch,var(--background) 72%,transparent)}
/* A header that CONTAINS the tab bar has no border of its own: width 0, not a
   transparent colour. `transparent` gives up the colour but keeps the 1px of SPACE, which
   is invisible over an opaque ground and not invisible at all under .scrolled's 72%-
   transparent + blur(20px) surface — the content scrolling underneath shows through that
   1px as a second, blurred strip below the tab bar's real line. Reported from two products.
   Zeroing the WIDTH on the :has() (not on .scrolled) keeps both states the same height, so
   nothing shifts when the class lands. Measured at 390px scrolled: tabs end at 71.9, header
   at 72.9 -> both at 71.9. (labs §V18-1) */
.re-page-header:has(.re-page-tabs){border-bottom-width:0}
/* The eyebrow's type lives in ONE place and two things read it: the label, and the back
   control that replaces it on a detail page. A sidebar-less product has nowhere else to put
   "back", and this slot is where a consumer already put it — with re-btn-text, which is the
   wrong class for a measurable reason: it is re-btn's 0.8125rem plus `padding:0 0 3px`, so in
   an 0.6875rem slot with no padding it made the row taller and the title move. The button
   language is right, the button class is not.
   Declared as one selector list rather than copied, because a copied constant is what this
   file keeps getting caught by. (kullanıcı isteği, 2026-09-08) */
.re-ph-eyebrow,.re-ph-back{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-foreground)}
/* A <button> or an <a>: reset the control, keep the eyebrow's metrics, and take the underline
   affordance from re-btn-text without its size. `line-height:inherit` is the load-bearing
   part — the chevron is 12.6px against an 11px label, so without it the icon would set the
   row's height and the eyebrow line would grow.
   ⚠ `display:flex` + `width:fit-content`, NOT inline-flex, and this was measured: as an
   inline-flex child of the block-level re-ph-body it sat in a line box and the strut added
   7.5px to the header even though the control itself measured the eyebrow's exact 16.5px.
   Block-level with a shrink-to-fit width keeps the box its own size AND keeps the underline
   the width of the label rather than the row. */
.re-ph-back{display:flex;width:fit-content;align-items:center;
  height:auto;padding:0;border:0;background:none;border-radius:0;
  font-family:inherit;line-height:inherit;text-decoration:none;cursor:pointer;
  position:relative;transition:color .15s}
/* NO ARROW by default, and the label itself aligns with the title. This reverses the previous
   round, and the reason is that the CRITERION changed rather than the measurement: that round
   asked "what does the arrow align to" and answered it correctly (the glyph's ink starts at
   1/3 of its box — chevron-left is drawn from x=9 of a 24 viewBox with a 2-unit centred
   stroke; rastered at 8x, ink runs 4.22→8.41 in a 12.65px box, so -1/3 * --re-icon pulls the
   INK onto the title's column and scales with density). It aligned the arrow, and therefore
   pushed the LABEL one glyph to the right — which is what a review then read as the alignment
   error, twice. The eyebrow slot's job is to NAME the parent, not to draw a control, so the
   thing that has to line up is the word.

   An <svg> is still handled rather than ignored, because consumer markup already has one: it
   is taken out of flow into the gutter, so old markup gets the aligned label too instead of
   breaking. That is an opt-in for ink in the page margin — the same thing re-main-bleed and
   re-section-bleed exist to be — and it is the consumer's choice, not the default.
   The affordance is the underline on hover (::after below), which is re-btn-text's. */
.re-ph-back > svg{position:absolute;left:calc(-1 * (var(--re-icon) + var(--re-space-1)));
  top:50%;translate:0 -50%;width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
/* The hit area is a pseudo-element, not padding, and the reason is the rhythm: this control
   sits in the eyebrow slot and its box height IS the eyebrow's, so padding would push the
   title down and break the alignment the comment above spends fifteen lines establishing.
   An 11px uppercase label measures 16.5px tall — under WCAG 2.5.8 (AA, 24x24 CSS px) by
   7.5px, and it is the only way back to the list on a phone.

   `max(100%, var(--re-tap-min))` rather than a negative inset: the label's height moves with
   --font-scale and density, so any fixed offset is right at one setting and wrong at the
   others. Width needs nothing — the label is already wider than 24px, and `left/right:0`
   keeps the target the width of the word rather than the row.

   ::BEFORE, because ::after is the underline. A control with two jobs for its two pseudo-
   elements has none left; if a third is ever needed, the underline moves to a real element. */
.re-ph-back::before{content:"";position:absolute;left:0;right:0;top:50%;translate:0 -50%;
  height:max(100%, var(--re-tap-min))}
.re-ph-back::after{content:"";position:absolute;left:0;right:100%;bottom:-1px;border-bottom:1px solid currentColor;transition:right .25s ease-out}
.re-ph-back:hover,.re-ph-back:focus-visible{color:var(--foreground)}
.re-ph-back:hover::after,.re-ph-back:focus-visible::after{right:0}
/* v2 §2.9 — page-header title + subline (the eyebrow existed; these were written inline in the DS's own dashboard demo). */
.re-ph-title{font-size:calc(1.5rem * var(--font-scale));font-weight:800;line-height:1.2;color:var(--foreground);margin:2px 0 0}  /* 2px sits it just under the eyebrow; negligible when it's the first child */
.re-ph-sub{font-size:calc(0.875rem * var(--font-scale));color:var(--muted-foreground);margin:4px 0 0}
/* v10 — an inline icon in the page subline, solved the way re-row-sub solves it rather than
   with flex. A consumer asked for this having added display:flex+align-items+gap+flex-wrap at
   every icon site — their largest local block — and my first attempt was exactly that. It is
   the wrong tool here: flex makes every inline child its own item, which is the contract that
   bit re-hint in v8 §V8-2 (67px of overflow from an icon plus two <strong>s). A subline is a
   line of TEXT that happens to start with an icon, so the icon aligns to the baseline and the
   text wraps by word like text should. */
/* vertical-align did NOTHING here and never could: the base reset makes every svg
   `display:block`, and vertical-align only affects an inline box. Measured — the property
   computed (-2.24px) and the icon still took its own block, 300px wide in a 300px column,
   because it had no size either. Two silent failures on one line: an inert property, and the
   unsized-slot family again.
   The reset's own principle is the fix: "every icon in this system is a box next to text,
   never a glyph inside a run of it." So the sub becomes a flex row WHEN it leads with one —
   re-hint's shape — rather than the reset being fought. :has(), so a sub with no icon keeps
   being plain prose and no consumer's markup changes. (labs §V15-2) */
/* A trailing BADGE has the same problem as the leading icon and needs the opposite
   alignment. re-badge is `inline-flex`, and an inline-flex box's baseline is its own inner
   text's baseline — the badge's line-height is 1 on an 11px font, the surrounding line box
   is 17px, so when the two baselines meet the badge's BOX sits low. Measured at 390px:
   +1.00px, and vertical-align cannot repair it (measured: -1px overshoots to +2.00 AND
   grows the line box to 24px, because vertical-align moves the box inside the line rather
   than within it; `middle` and `text-bottom` were measured by the consumer and are worse or
   inert). Flex is the only mechanism that reaches 0.00, and it wants `center` here where the
   icon wants `flex-start` — a badge is a label that rides the line, an icon is a marker on
   its first line.
   ⚠ The cost, measured, is real: flex makes the text ONE item, so on a sub long enough to
   wrap the badge is pushed to a flex line of its own — 65px -> 92px at 390px. Inline flow
   would have kept it at the end of the last line. That is the re-hint trap of v8 §V8-2, paid
   deliberately: a subline carrying a status chip is short, and a LONG prose sub should carry
   its chip in the eyebrow, not trailing the sentence.
   Declared BEFORE the icon rule so a sub with both keeps the icon on its first line
   (measured: icon at +2.00 from the sub's top either way, badge +1.00 in that combination —
   the icon's contract is the older one and wins the tie). (labs §V16-1) */
:is(.re-ph-sub,.re-row-sub):has(> .re-badge){display:flex;align-items:center;gap:6px;flex-wrap:wrap}
:is(.re-ph-sub,.re-row-sub):has(> svg:first-child){display:flex;align-items:flex-start;gap:6px;flex-wrap:wrap}
:is(.re-ph-sub,.re-row-sub) > svg:first-child{width:var(--re-icon);height:var(--re-icon);flex-shrink:0;margin-top:2px}
/* v10 gave the icon'd subline flex + flex-wrap, and its comment said wrap was wanted "so wrapping
   degrades gracefully instead of pushing text under the icon". Measured, wrap is what PUSHES the
   text under the icon: the text is one anonymous flex item, it does not break, it moves to a flex
   line of its own. At 390px a two-line sub put its first character 23.8px BELOW the icon's top,
   at x=0 (labs §V42-1, three consumers). The justification described the opposite of what the
   rule does.

   Dropping the wrap is not the fix, and that is the part a plain-text test hides. With nowrap
   a sub whose text carries inline markup (`… passkey <strong>ID</strong>'den …`) becomes THREE
   flex items side by side, each wrapping in its own column — measured, three line-start x's
   (22 / 146 / 179) where a paragraph has one. It passes on plain text and fails on the first
   bold word.

   So the icon-only case takes re-hint's mechanism, the one re-section-block > .re-hint already
   uses: the icon sits absolutely in a reserved slot and the text stays in NORMAL FLOW, one
   paragraph whatever it contains. The slot is 1em x --re-icon-ratio + the old 6px gap, read on
   the sub itself — `--re-icon` is declared on the svg, so reading it here would get nothing —
   and a short sub's text lands where it did before (21.8 either way).

   The badge case keeps flex: its rule above is a measured, documented trade (flex is the only
   mechanism that puts a chip's baseline at 0.00). A sub with both an icon and a badge still
   matches the flex rule, unchanged. */
:is(.re-ph-sub,.re-row-sub):has(> svg:first-child):not(:has(> .re-badge)){display:block;position:relative;padding-left:calc(1em * var(--re-icon-ratio) + 6px)}
:is(.re-ph-sub,.re-row-sub):has(> svg:first-child):not(:has(> .re-badge)) > svg:first-child{position:absolute;left:0;top:0}
.re-ph-body{
  padding-block:calc(16px * var(--density-mult,1));
  padding-left:calc(var(--re-gutter) + var(--re-chrome-bleed-l,0px));
  padding-right:calc(var(--re-gutter) + var(--re-chrome-bleed-r,0px));
}   /* heading block — the same token the navbar and the content column use. It held --pad-x, which is the CONTROL padding (18px at standard) and not the page gutter (27px), so the h1 sat 9px left of the brand above it in the bare (containerless) form. A duplicated constant that happens to agree is not an alignment — the navbar's own comment below says this about the same 9px. */

/* ══ Collapsing page header (re-page-header-collapse) ══════════════
   The topbar scrolls away and the heading takes over the space it leaves: the title shrinks
   to the brand's size, the subline is deleted, and what is left stays stuck to the top. Two
   consumers built this independently, byte for byte, because the shell gave them no way to
   do it — the canonical app-topbar puts re-navbar OUTSIDE re-main, so it never scrolls, and
   at 390px navbar 56 + header 126 = 182px of permanently spent screen (47%).

   The markup change is the whole first half: move re-navbar INSIDE re-main, as its first
   child. It then scrolls with the content like anything else, and re-page-header's existing
   position:sticky does the rest — that part was always ready.

   The second half is this variant plus one number, --re-ph-t, the collapse progress from 0
   to 1. CSS reads it; initCollapseHeaders() writes it. The threshold is THE DISTANCE THE
   COLLAPSE HAS TO FINISH IN, never a constant — or the heading either lands early and jumps
   or lands late and the top of the screen is briefly empty.

   ⚠ In the canonical shell that distance is the navbar's own offsetHeight, and this comment
   used to say so as if the two were the same thing. They are not: the navbar's height is the
   common IMPLEMENTATION of the rule, and a consumer that hides its topbar on phones (a user
   decision, not a deviation) has no such distance at all. There the threshold is the header's
   own travel — its height at t=0 minus its height at t=1 — which is what the rule was always
   measuring. Two consumers derived that independently before it was written down.

   Density resizes both ends, so the number is re-measured on resize rather than cached.

   ⚠ re-pull (pull-to-refresh) is position:absolute; top:0 in the same scroller, so it and a
   navbar-inside-main now start from the same point. Measured: no collision, because the
   pull indicator is only visible while you are dragging DOWN and at that moment the scroller
   is already at the top with the navbar in place. Anything that shows re-pull at a non-zero
   scrollTop would have to say which of the two owns the top. (labs §V18-2) */
.re-page-header-collapse{--re-ph-t:0}
/* Scroll anchoring has to be off in this scroller, and this is not a nicety. The header
   shrinking is a size change ABOVE the anchor node, so the engine "helpfully" subtracts the
   shrinkage from scrollTop to keep the content still — which lowers scrollTop, which lowers
   --re-ph-t, which grows the header back. Measured with the navbar at 56px: at scrollTop 56
   the header should be flush at top 0 and instead sat at 34, because 34px of collapse had
   been handed back as scroll. With overflow-anchor:none the same scroll lands at exactly 0,
   which is the entire contract — the collapse finishes in the distance the navbar takes to
   leave. Scoped by :has() so every other re-main keeps anchoring. */
.re-main:has(.re-page-header-collapse){overflow-anchor:none}
.re-page-header-collapse .re-ph-body{
  padding-block:calc((16px - 6px * var(--re-ph-t)) * var(--density-mult,1));
}
.re-page-header-collapse .re-ph-title{font-size:calc((1.5rem - 0.5rem * var(--re-ph-t)) * var(--font-scale))}
/* max-height AND opacity: opacity alone leaves the height behind, so the header keeps a
   translucent band of nothing where the subline was — the consumer hit this first. */
.re-page-header-collapse .re-ph-sub{
  overflow:hidden;
  max-height:calc(3em * (1 - var(--re-ph-t)));
  margin-top:calc(4px * (1 - var(--re-ph-t)));
  opacity:calc(1 - var(--re-ph-t));
}
/* Chrome that lives INSIDE the scroller has to take back the scrollbar gutter re-main
   reserves on each edge, or it is inset from the pane while the fixed chrome above it was
   not — and for a page header that means its tab rule stops short of both edges, which
   A-47 says it must not (labs §V14-4 is the same line, one level out). Measured in a 388px
   scroller with an 11px gutter: the rule spans 366px, 11px short on each side.
   This is what re-main-bleed does, applied for you, because both of these are full-bleed by
   definition and there is no case where you would want the gutter on them. re-main-bleed
   stays for everything else, and for a header that is NOT a direct child of the scroller.
   Direct children only: something nested inside a content column belongs to that column. */
/* ⚠ `.re-shell-body > .re-navbar` REMOVED (TB1, 2026-10-06). The sidebar shell's navbar is re-main's sibling, outside the
   scroller; borrowing main's scrollbar reservation moved its inner edges with the page (md 287 / full 283 at 1280) while the
   footer, which never read it, stayed at 283 in every view. Ersin: "full width container'ın margin-left-right topbar'da da
   mainde de footer'da da aynı olmalı … topbara hiçbir şekilde dokunmuyor olmalıyız." The reservation is main's own business
   and is paid inside main; the navbar and the footer sit on --re-gutter, always. */
.re-main > .re-navbar,
.re-main > .re-page-header{
  --re-chrome-bleed-l:var(--re-main-gutter-l,0px);
  --re-chrome-bleed-r:var(--re-main-gutter-r,0px);
}
.re-main > .re-navbar,
.re-main > .re-page-header{
  margin-left:calc(-1 * var(--re-chrome-bleed-l,0px));
  margin-right:calc(-1 * var(--re-chrome-bleed-r,0px));
}
/* Reaching the edge is only half of it: a strip that spans the reserved gutter has to put
   its TEXT back where the content column's text is, or the app's own name sits a scrollbar's
   width left of everything under it. So bled chrome pads --re-gutter PLUS the width it just
   took back. Measured at 1280px with an 11px gutter, before: navbar/title/tabs text at 27,
   content column at 38 — and this was true of the DS's own dash-tabs showcase, invisible on
   macOS because overlay scrollbars make the gutter 0.
   --re-chrome-bleed-* is declared on the strip itself, never on :root: it reads
   --re-main-gutter-*, which lives on re-main (an ancestor for a strip inside it) and on
   re-shell-body (for the canonical navbar, which is main's SIBLING and cannot see main's).
   A header that is not bled never declares it, so it keeps padding exactly --re-gutter. */
/* v7 §V7-7 — action slot to the right of the identity block (view switch, primary
   action). No wrapper around the text: the eyebrow/title/sub auto-place into column 1
   one per row, and the slot spans every row in column 2, so it centres against the
   whole block. Written as :has() on the actions element, so a header without one is
   byte-for-byte what it was. The .container form is spelled out too — the full-width
   variant puts the slot inside the container, not in re-ph-body.
   Container query, not a media query: this header sits beside a sidebar, so the
   viewport is the wrong ruler (same lesson as re-board). */
.re-page-header{container-type:inline-size}
/* Take back the scroller's reserved gutter. re-main reserves a scrollbar's width
   (scrollbar-gutter: stable) so the column never shifts, and a sticky page header inside the
   scroller is inset by it, so its bottom rule stops short of the shell's edge. Two tokens,
   not one, because the reservation is NOT symmetric — with `stable` it is on the inline-end
   edge only, and even under `both-edges` an engine that ignores it took the whole width on
   the right. So initMainGutter() probes each side and publishes --re-main-gutter-l / -r; the
   strip then owes exactly what it took on that side, and pays it back as padding so its text
   returns to the content column. With no JS both are 0 and nothing moves. */
.re-main-bleed{
  --re-chrome-bleed-l:var(--re-main-gutter-l,0px);
  --re-chrome-bleed-r:var(--re-main-gutter-r,0px);
  margin-left:calc(-1 * var(--re-chrome-bleed-l,0px));
  margin-right:calc(-1 * var(--re-chrome-bleed-r,0px));
}
/* ⚠ Not tried again: `.re-main-bleed > .container{padding-left:calc(gutter + bleed)}` to fix a
   hand-rolled strip. It cannot win — `.container` lives in @layer UTILITIES on purpose (see
   its own note in base.css) and this rule is in `components`, so it computes to nothing at
   equal specificity. Measured: 27px, unchanged. A bled wrapper of your own has to pay the
   bleed as padding itself, or wrap its content in re-ph-body, which already does. */
.re-ph-body:has(> .re-ph-actions),.re-ph-body > .container:has(> .re-ph-actions){display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-rows:repeat(3,auto);column-gap:var(--re-space-4);align-items:center}  /* rows are EXPLICIT on purpose: the slot spans 1/-1, and -1 does not resolve in an implicit grid — it silently collapses to a single row and the slot stops being centred. Three rows = the eyebrow/title/sub trio; empty ones measure 0. */
.re-ph-body:has(> .re-ph-actions) > :not(.re-ph-actions),.re-ph-body > .container:has(> .re-ph-actions) > :not(.re-ph-actions){grid-column:1}
/* ⚠ Three rows is right when there are three things to put in them, and WRONG when there is
   one. The slot spans 1/-1, and a spanning grid item taller than the tracks it covers has its
   extra space DISTRIBUTED across them (CSS Grid §11.5) — so with a title alone, a 42px button
   over a 31px title gave rows of 30.8 / 5.6 / 5.6 and `align-items:center` centred the title
   in row ONE. The property was doing its job; the row it was centring in was the wrong one.
   Measured at 1280px, title's centre against the band's: **−4.6px**, i.e. the heading sits in
   the upper third of a band it should be centred in. Reported on two products, neither of
   which shrinks its title — the collapse variant deepens this, it does not cause it.

   Two candidate fixes measured and REJECTED, both of which look right on paper:
     grid-template-rows: auto 1fr auto  → title_off −4.6, UNCHANGED. The extra moves from
       (5.6, 5.6) to (11.2, 0), but the title is in row 1 either way, so it cannot move.
     align-content: center              → title_off −4.6, UNCHANGED. There is no free space
       for it to pack: the container is auto-height, sized BY the tracks after the spanning
       item has already grown them. (Third appearance of align-content this week, and the
       first time it is not the answer.)

   What works is giving the grid as many rows as it has lines: with one row the title and the
   slot share it and `align-items:center` centres the shorter one. Measured: row 42px,
   title_off **+1.0** — and that residual is the glyph box, identical in both variants, not
   the grid.

   The count is of the BODY's children, not of eyebrow/sub by name: one line plus the slot is
   two children. That keeps a consumer's own single line working, and it is the same counting
   idiom this file already uses for the control size below.

   Two of three needs nothing, and that was measured rather than assumed: eyebrow+title bands
   at 79.3px and title+sub at 87.8px are both taller than the 42px slot, so there is no extra
   to distribute — group offset 0 and 1px, with the third row already at 0px. */
.re-ph-body:has(> .re-ph-actions):not(:has(> :nth-child(3))),.re-ph-body > .container:has(> .re-ph-actions):not(:has(> :nth-child(3))){grid-template-rows:auto}
.re-ph-actions{grid-column:2;grid-row:1/-1;align-self:center;display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--re-space-2)}
/* ONE control in the slot is the page's action and gets full weight — "New event", "New note",
   the thing a phone would put in a FAB. TWO or more and the slot is a toolbar: the screen is
   the same width, the controls are not the point of the page, and at md they crowd the title.
   So the size follows the count instead of being remembered, and it reads the named -sm step
   rather than repeating its numbers. (kullanıcı kuralı, 2026-09-08)
   `:has(> :nth-child(2))` is "has a second child", i.e. two or more. A consumer who wants the
   big form anyway writes re-btn-lg, which is more specific than this. */
/* ⚠ The COUNT is of the slot's own children — those are the controls, by contract — but the
   controls themselves are NOT required to be direct children, and the first version required
   it. An overflow menu is `re-dropdown > re-btn`, so the button sat one level down and stayed
   at 42px next to a 26px segment. Reported from a consumer, and it is the THIRD showing of
   this same assumption (§V22-1's centred column, a consumer's re-page-tabs-scroll, this) —
   each time the class works PARTLY, which is exactly why it looks like it is in the right
   place. */
.re-ph-actions:has(> :nth-child(2)) .re-btn:not(.re-btn-lg){--_h:var(--re-ctl-sm-h);--_px:var(--re-ctl-sm-px);--_fs:var(--re-ctl-sm-fs)}
.re-ph-actions:has(> :nth-child(2)) .re-segment:not(.re-segment-lg) .re-segment-item{--_h:var(--re-seg-sm-h);--_px:var(--re-seg-sm-px);--_fs:var(--re-seg-sm-fs)}
@container (max-width:560px){
  .re-ph-body:has(> .re-ph-actions),.re-ph-body > .container:has(> .re-ph-actions){grid-template-columns:minmax(0,1fr)}
  .re-ph-actions{grid-column:1;grid-row:auto;justify-content:flex-start;margin-top:calc(10px * var(--density-mult,1))}
}
.re-page-tabs{display:flex;flex-wrap:wrap;border-bottom:1px solid var(--border);padding:0 calc(var(--re-gutter) + var(--re-chrome-bleed-r,0px)) 0 calc(var(--re-gutter) + var(--re-chrome-bleed-l,0px));gap:1.25rem}
/* v5 §V5-7 — one row that scrolls instead of wrapping. Reported when a fifth tab pushed the
   last one onto a second line at 390px and the heading band grew; the consumer's users chose
   horizontal scroll over a select (two taps). Put this on whichever element is the flex row:
   re-page-tabs itself, or the inner .container in the full-width variant — the bottom border
   stays on re-page-tabs either way, so the rule never scrolls out of view.
   The scrollbar is hidden on purpose: this is chrome, and a 4px bar under the active tab's
   underline reads as a rendering fault. scroll-padding keeps the first tab off the edge.
   NOTE: this was marked closed in the v5 report on the strength of its commits without
   checking that the class existed — it did not. Verified by grep this time. */
.re-page-tabs-scroll{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scroll-padding-inline:var(--re-gutter);scrollbar-width:none;-ms-overflow-style:none}  /* v10 — overflow-y:hidden is NOT redundant: per spec, setting one axis to auto turns the other's `visible` into `auto`, so overflow-x alone made the strip vertically scrollable too. Measured at 390px: scrollHeight 38 vs clientHeight 34, because a tab is 35px with margin-bottom:-1px in a 34px box — a stray vertical drag shifted the tabs 4px against the bottom border. Reported by a consumer, and it is the other axis of the very thing this class's comment worries about. Any component that scrolls one axis has to pin the other. `hidden` and not `clip`: with a scrolling axis beside it, `clip` computes back to `hidden` anyway (measured). */
.re-page-tabs-scroll::-webkit-scrollbar{height:0;display:none}
.re-page-tabs-scroll > .re-page-tab{flex-shrink:0}
/* v12 §V12-1 — the active tab's underline did not replace the rule: it painted 2px of
   primary and left the strip's grey fully visible underneath. A border lives OUTSIDE the
   clip region, so inside a scroller the tab can never paint over it — no amount of
   negative margin reaches it, and `getBoundingClientRect` is blind to the whole class of
   bug (identical geometry with and without the class; only a rasterised pixel read shows
   it). So the rule moves INSIDE the scrollport as an inset shadow, where the tab's own
   2px underline covers it, and the tab stops overhanging. The shadow paints on the
   scrollport rather than the scrolled content, so the v5 requirement — the rule never
   scrolls out of view — still holds. In the full-width variant the class sits on the
   inner .container, so the parent's border is dropped there too: one rule, spanning the
   column the tabs are actually in. Measured by pixel probe, six consumers affected. */
/* v14 §V14-4 — the v12 fix above was written for ONE of the two placements and broke the
   other. Where the class sits decides which element owns the rule, so the two cases are now
   separate:

   (a) class ON re-page-tabs — that element IS the full-width one, so moving its rule inside
       as an inset shadow keeps it full width AND lets the indicator cover it. Unchanged.

   (b) class on the inner .container (the full-width variant, and what consumers use) — the
       OUTER element must keep its border, or the rule stops at the column and the tab strip
       no longer reads as full width. Measured by pixel probe: the gutter had NO line at all.
       So the border stays, and the scroller is nudged instead: 1px of padding-bottom moves
       the clip region down far enough that the tab's own -1px overhang is no longer cut,
       and -1px of margin-bottom pulls the whole thing back so the outer border lands in the
       SAME band the indicator paints over. Net: one line, full width, and the active tab
       still replaces it.
   ⚠ Do not "simplify" this by zeroing the tab's margin in case (b): the indicator then sits
      1px above the outer border and you get two lines. That was measured too, and geometry
      probes call it aligned — only a rasterised read shows the second line. */
.re-page-tabs.re-page-tabs-scroll{border-bottom:0;box-shadow:inset 0 -1px 0 var(--border)}
.re-page-tabs.re-page-tabs-scroll > .re-page-tab{margin-bottom:0}
.re-page-tabs > .re-page-tabs-scroll{padding-bottom:1px;margin-bottom:-1px}
/* Full-width header/tabs that line up with a centered content column: drop the
   own x-padding and let an inner .container carry the width + gutter (like re-navbar). */
/* The contained form hands the side padding to the .container — but a BLED strip still owes
   the gutter it took back, and the container cannot pay it (it would then be paying twice on
   an unbled strip). So the strip keeps exactly the bleed and the container adds --re-gutter
   on top: 11 + 27 = 38, the content column. Zero when nothing was bled, which is what this
   line used to say unconditionally. */
.re-ph-body:has(> .container),.re-page-tabs:has(> .container),.re-navbar:has(> .container){
  padding-left:var(--re-chrome-bleed-l,0px);
  padding-right:var(--re-chrome-bleed-r,0px);
}
/* …and the container has to become the flex row, or the bar's `gap` applies between the bar
   and its single child and every tab crams together. Dropping the padding was only half of
   the handover: the tabs are now the CONTAINER's children, so the layout has to move with
   them. `inherit` rather than a second 1.25rem — one number, one place. */
/* :not(.re-page-tabs-scroll) — flex-wrap is the ONE property this handover must not pass
   down, because -scroll is what decides it. Inherited, the contained form ignored -scroll
   entirely: measured, flex-wrap stayed `wrap` while overflow-x became `auto`, so the strip
   wrapped and therefore never needed to scroll. Reported by labs (§V15-3) against a rule
   added the same day this file gained the handover, and visible in our own dash-tabs layout.
   The other three properties SHOULD pass down — they are the row's layout, and the row moved. */
.re-page-tabs > .container{display:flex;gap:inherit;align-items:inherit}
.re-page-tabs > .container:not(.re-page-tabs-scroll){flex-wrap:inherit}
.re-page-tab{padding:7px 0;font-size:calc(0.8125rem * var(--font-scale));font-weight:500;color:var(--muted-foreground);cursor:pointer;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;background:none;font-family:inherit;display:inline-flex;align-items:center;gap:6px;transition:color .1s}
.re-page-tab:hover{color:var(--foreground)}
.re-page-tab.active{color:var(--primary);border-bottom-color:var(--primary)}
.re-page-tab:focus-visible{outline:2px solid var(--c-focus,var(--ring));outline-offset:2px}
.re-tab-icon{width:var(--re-tab-icon,15px);height:var(--re-tab-icon,15px);flex-shrink:0}
.re-tab-count{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);background:color-mix(in oklch,var(--muted-foreground) 14%,transparent);border-radius:var(--radius-full);padding:1px 7px;line-height:1.5}
.re-page-tab.active .re-tab-count{color:var(--primary);background:color-mix(in oklch,var(--primary) 15%,transparent)}
.re-tab-soon{color:var(--muted-foreground);opacity:.65;cursor:default;pointer-events:none}

/* ══ re-period-nav — moving through periods, under the strip that names them (§V28-2) ══
   Born from a measurement, so the reason is worth keeping: a consumer put this control in
   re-ph-actions and the header JUMPED 42px between tabs at 390px (Agenda 123.3px, Month
   165.3px), because the period label is not one width — "Week 15" / "May 2026" / "2026".
   The size-by-count rule did the right thing and the result was still wrong: the defect was
   the ROLE. A period navigator governs what the whole view shows, tab selection included, so
   it sits UNDER what it governs — see page-header.mdx for the rule that follows from this.

   Order is [Today] [‹][›] <period>, and it is deliberate: the absolute jump first, then the
   relative steps, then where you landed. The label comes LAST of the three so that its
   changing width pushes nothing — everything that can be clicked is to its left. */
.re-period-nav{display:flex;align-items:center;flex-wrap:wrap;gap:var(--re-space-3);
  padding-block:var(--re-space-3);
  /* Its own container, so the narrow arrangement below keys off THE BAR's width and not the
     viewport's — the same bar in a narrow split pane has to break at the same point it would
     on a phone. */
  container-type:inline-size}
/* A pair, tighter than the gaps between groups. Without this the two arrows sit exactly as
   far from each other as [Today] sits from them, and four controls read as four unrelated
   buttons instead of one jump + one pair. */
.re-period-step{display:flex;align-items:center;gap:var(--re-space-1)}
/* tabular-nums: stepping through 9 → 10 → 11 must not shift the text. */
.re-period-label{font-size:calc(0.9375rem * var(--font-scale));font-weight:600;
  color:var(--foreground);font-variant-numeric:tabular-nums;min-width:0}
.re-period-label-sub{margin-left:var(--re-space-2);font-size:calc(0.75rem * var(--font-scale));
  font-weight:500;color:var(--muted-foreground);font-variant-numeric:tabular-nums}
/* The free slot — a filter, a source switch. margin-left:auto pins it right, and it keeps
   doing that after the row wraps on a phone (it lands on its own line, still right-aligned). */
.re-period-end{margin-left:auto;display:flex;align-items:center;gap:var(--re-space-2);
  flex-wrap:wrap}
/* Narrow: the CONTROLS keep one row and the LABEL drops to its own. Left to plain wrapping the
   bar broke in the wrong place — the free slot went down and the label stayed up, so the row
   that moved was the one you press. Here the controls stay together (Today · steps · slot,
   the slot still right-aligned by margin-left:auto) and the reading moves, which is the half
   that is only read. `order` + `flex-basis:100%` rather than a second markup shape. */
@container (max-width:430px){
  .re-period-label{order:1;flex-basis:100%;margin-top:calc(-1 * var(--re-space-1))}
}
/* tiny in-tab label pill (soon / new) — neutral, doesn't recolor on active */
.re-tab-badge{font-size:calc(0.625rem * var(--font-scale));font-weight:600;line-height:1.4;padding:1px 6px;border-radius:var(--radius-full);background:color-mix(in oklch,var(--muted-foreground) 15%,transparent);color:var(--muted-foreground);text-transform:uppercase;letter-spacing:.04em}

/* ══ Second sidebar (re-second-sidebar) — resizable + collapse-rail ══
   Secondary nav column: scroll area (.re-ss-nav > .re-ss-inner) + footer. Drag
   .re-ss-resize (right edge) to resize 200–380px; collapse to a 56px rail with
   .re-ss-collapsed (peek on hover). Items: re-sec-item / -icon / -chevron;
   collapsible groups: re-sec-section > re-sec-children > re-sec-sub. */
.re-second-sidebar{width:220px;min-width:200px;flex-shrink:0;border-right:1px solid var(--border);background:var(--background);display:flex;flex-direction:column;overflow:visible;position:relative;transition:width .18s ease}
.re-second-sidebar a{text-decoration:none}
.re-second-sidebar a:hover{text-decoration:none}
/* Two levels of nav do not fit a phone. Where the primary sidebar becomes a drawer
   (re-sidebar-drawer), the second one steps out entirely — measured at 390px it left the
   article 170px. Same breakpoint as the drawer, deliberately: they are one decision. */
@media (max-width:860px){ .re-second-sidebar{display:none} }
.re-second-sidebar .re-ss-nav{flex:1;overflow-y:auto;overflow-x:hidden;padding:12px 8px}
.re-second-sidebar .re-ss-inner{display:flex;flex-direction:column;gap:2px}
.re-sec-item{display:flex;align-items:center;white-space:nowrap;min-height:32px;padding:5px 12px;border-radius:var(--radius-inset);cursor:pointer;color:var(--foreground);text-decoration:none;font-size:13px;font-weight:500;background:none;border:none;font-family:inherit;width:100%;text-align:left;transition:color .1s}
.re-sec-item:hover,.re-sec-item.active{color:var(--primary)}
.re-sec-icon{display:flex;align-items:center;justify-content:center;width:16px;flex-shrink:0;margin-right:8px}
.re-sec-chevron{width:12px;height:12px;flex-shrink:0;color:var(--muted-foreground);transition:transform .15s;margin-left:auto}
.re-sec-section{position:relative}
.re-sec-section.open::before{content:'';position:absolute;left:20px;top:29px;bottom:4px;width:1px;background:var(--border)}
.re-sec-section.open>.re-sec-item>.re-sec-chevron{transform:rotate(90deg)}
.re-sec-children{display:none;margin-top:1px}
.re-sec-section.open .re-sec-children{display:block}
.re-sec-sub{display:block;white-space:nowrap;padding:4px 10px 4px 36px;border-radius:var(--radius-inset);cursor:pointer;color:var(--foreground);text-decoration:none;font-size:12px;font-weight:400;transition:color .1s}
.re-sec-sub:hover,.re-sec-sub.active{color:var(--primary)}
.re-second-foot{height:44px;flex-shrink:0;border-top:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:8px}
.re-ss-resize{position:absolute;right:-3px;top:0;bottom:0;width:6px;cursor:col-resize;z-index:20;transition:background .15s}
.re-ss-resize:hover,.re-ss-resize.dragging{background:color-mix(in oklch,var(--primary) 25%,transparent)}
.re-second-sidebar.re-ss-collapsed{width:56px !important;min-width:56px !important}
.re-second-sidebar.re-ss-collapsed:has(.re-ss-inner:hover){width:220px !important}
.re-second-sidebar.re-ss-collapsed:not(:has(.re-ss-inner:hover)) .re-sec-item{font-size:0}
.re-second-sidebar.re-ss-collapsed:not(:has(.re-ss-inner:hover)) .re-sec-icon{margin-right:0}
.re-second-sidebar.re-ss-collapsed:not(:has(.re-ss-inner:hover)) .re-sec-chevron{display:none}
.re-second-sidebar.re-ss-collapsed:not(:has(.re-ss-inner:hover)) .re-sec-children{display:none}
.re-second-sidebar.re-ss-collapsed .re-second-foot{justify-content:center;padding:0}
.re-second-sidebar.re-ss-collapsed .re-ss-resize{display:none}

/* ══ Live code playground (re-playground) ══
   Editable code (textarea) + sandboxed live preview (iframe). The runtime injects
   your stylesheets (data-css hrefs, fetched + inlined) so the preview is on-brand.
   Add re-playground-preview-only to hide the editor and auto-size the iframe. */
.re-playground{border:1px solid var(--border-surface);border-radius:var(--radius);overflow:hidden;background:var(--card)}
.re-playground-bar{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--surface)}
.re-playground-title{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);text-transform:uppercase;letter-spacing:.06em}
.re-playground-reset{font-size:calc(0.75rem * var(--font-scale));padding:3px 10px;border-radius:var(--radius-inset);border:1px solid var(--border);background:var(--card);color:var(--muted-foreground);cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s}
.re-playground-reset:hover{color:var(--foreground);border-color:var(--primary)}
.re-playground-main{display:grid;grid-template-columns:1fr 1fr}
@media(max-width:720px){.re-playground-main{grid-template-columns:1fr}}
.re-playground-editor{border:0;outline:none;resize:vertical;padding:14px;font-family:var(--font-mono);font-size:12.5px;line-height:1.6;background:var(--surface-alt);color:var(--foreground);min-height:240px;width:100%;border-right:1px solid var(--border);tab-size:2}
@media(max-width:720px){.re-playground-editor{border-right:0;border-bottom:1px solid var(--border)}}
.re-playground-preview{border:0;width:100%;min-height:240px;background:var(--background);display:block}
.re-playground-preview-only .re-playground-main{grid-template-columns:1fr}
.re-playground-preview-only .re-playground-editor{display:none}
.re-playground-preview-only .re-playground-preview{min-height:0}

/* ══════════════════════════════════════════════════════════════════
   Mobile app components (from the _labs gap report §2/§3) — the phone
   counterparts of the desktop chrome. All density-aware and re-c-{role}
   friendly; designed to compose inside re-app / re-shell.
   ══════════════════════════════════════════════════════════════════ */

/* ── re-tabbar — bottom navigation (re-sidebar's phone counterpart) ──
   PLACEMENT CONTRACT (v3.2) — two placements, one component:
   (1) DEFAULT (flush / in-flow): the bar is the last flex child of the app column
       (.re-shell-body), edge-to-edge, with a top hairline. It TAKES layout space, so
       content can never hide under it and no clearance is needed. Carries the bottom
       safe-area inset itself. Use for system-style / data-dense apps.
   (2) re-tabbar-float (recommended for app shells): a rounded island that OVERLAYS the
       content, inset from the left/right/bottom edges by --re-tabbar-inset — the modern
       iOS bottom-bar shape. Needs a positioned ancestor (.re-shell-body already is) and
       bottom clearance on the scroll area, which the DS adds via :has() below.
   Both are translucent + backdrop-blurred with the SAME recipe as the scrolled page
   header (blur(20px) over a 72% surface), so content shows through while it scrolls
   under — visible in the float placement, inert in the flush one (nothing passes under
   an in-flow bar). Note the plain topbar (re-navbar) is deliberately opaque; the blurred
   surfaces are re-page-header.scrolled and this bar.
   Retune with --re-tabbar-h / --re-tabbar-inset (base.css) — the bar, the content
   clearance and the FAB lift all read the same two values. */
.re-tabbar{display:flex;align-items:stretch;flex-shrink:0;
  background:color-mix(in oklch,var(--card) 72%,transparent);
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  border-top:1px solid var(--border);padding-bottom:env(safe-area-inset-bottom)}
.re-tabbar-item{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:var(--re-tabbar-h);padding:10px 8px;
  background:none;border:none;font-family:inherit;font-size:11px;font-weight:500;color:var(--muted-foreground);text-decoration:none;cursor:pointer;transition:color .12s;
  white-space:nowrap;overflow:hidden}   /* v6 V6-4 — a label needs ~61px (11px "Settings" = 44.6px + 16px padding). Below that it used to spill over the NEIGHBOUR: nowrap keeps it off a second line (which would grow the bar and silently break the content clearance), overflow:hidden keeps the spill inside its own cell. 5 tabs on a ≤320px screen truncate — prefer 4 in the island, or the flush bar. */
.re-tabbar-item{--re-icon-ratio:2}   /* phone tab bar: the label is 11px and the icon leads — 22px, as today */
.re-tabbar-item svg{width:var(--re-icon);height:var(--re-icon)}
.re-tabbar-item.active,.re-tabbar-item[aria-current="page"]{color:var(--primary)}
.re-tabbar-badge{position:absolute;top:5px;left:calc(50% + 5px);min-width:16px;height:16px;padding:0 4px;display:inline-flex;align-items:center;justify-content:center;
  background:var(--danger);color:var(--danger-foreground);font-size:10px;font-weight:700;line-height:1;border-radius:999px}
.re-app .re-tabbar{display:none}                         /* phones only… */
@media (max-width:1023px){.re-app .re-tabbar{display:flex}}
/* v5 V5-5 — …and hide the topbar's own nav at the same breakpoint, otherwise the same targets
   show twice (menu above, tab bar below). Scoped to an re-app that actually HAS a tab bar, so a
   marketing/docs topbar with no bottom bar keeps its nav on phones. */
@media (max-width:1023px){.re-app:has(.re-tabbar) .re-navbar-nav{display:none}}
/* Reverse-tabbar: same equal split, but the active item is marked with a TOP
   border (a topbar tab-set flipped to the bottom). Pairs with re-navbar-nav-tabs. */
.re-tabbar-top .re-tabbar-item{border-top:2px solid transparent;margin-top:-1px}
.re-tabbar-top .re-tabbar-item.active,.re-tabbar-top .re-tabbar-item[aria-current="page"]{border-top-color:var(--primary)}
/* ── re-tabbar-pill — button-style items (current iOS bottom-bar feel) ──
   The highlight is a ::before LAYER inset inside the item, not the item's own box: the item
   keeps its full height, so the BAR stays --re-tabbar-h and the content clearance / FAB lift
   keep working (insetting the item itself pushed the bar to 59-64px). isolation:isolate keeps
   the z-index:-1 layer above the bar's own background.
   The inset is uniform on all four sides: 5px top/bottom from the layer, and 5px at the ends
   = the bar's 2px padding + the layer's 3px — the same 3px on both neighbours also leaves a
   6px gap between highlights. Composes with both placements. */
.re-tabbar-pill{padding-inline:2px}
.re-tabbar-float.re-tabbar-pill{--_bar-pad:2px}   /* the highlight provides the optical inset, so the island's 8px end padding isn't needed — a TOKEN, not a padding override, so the width calc stays correct */
.re-tabbar-pill .re-tabbar-item{isolation:isolate;transition:color .12s}
.re-tabbar-pill .re-tabbar-item::before{content:"";position:absolute;inset:5px 3px;z-index:-1;
  border-radius:var(--radius-control);transition:background .12s}
.re-tabbar-float.re-tabbar-pill .re-tabbar-item::before{border-radius:var(--radius-full)}   /* the highlight follows the container's shape: fully rounded inside the pill island, rounded-square inside the flush bar */
.re-tabbar-pill .re-tabbar-item:hover{color:var(--foreground)}
.re-tabbar-pill .re-tabbar-item:hover::before{background:color-mix(in oklch,var(--foreground) 6%,transparent)}
.re-tabbar-pill .re-tabbar-item.active::before,.re-tabbar-pill .re-tabbar-item[aria-current="page"]::before{background:color-mix(in oklch,var(--primary) 12%,transparent)}
.re-tabbar-pill .re-tabbar-item.active:hover::before,.re-tabbar-pill .re-tabbar-item[aria-current="page"]:hover::before{background:color-mix(in oklch,var(--primary) 18%,transparent)}
/* v6 V6-4 — the island's width is COMPUTED from the item token and the tab count, never
   derived from its content. It used to be `width:max-content` with `flex:0 0 <token>` items,
   which broke in WebKit ONLY: WebKit ignores flex-basis while computing a flex container's
   intrinsic contribution, so max-content came out as 4 x the *label* width (~52px = 226px)
   while the final layout applied the basis (4 x 76 = 304px). The 78px difference was eaten
   by the island's own overflow:hidden — no error, no warning, a whole tab missing. Measured
   at 402px: WebKit island 226px / 3 of 4 tabs visible, Chromium 322px / 4 of 4. Chromium
   measurements are NOT evidence for this rule.
   `flex-shrink:1` was tried and rejected: it fixes 320px and breaks 375px+, because
   max-content sizing then bases items on content instead of the basis.
   --re-tabbar-count (base.css, default 4) is the one thing a consumer sets; the item cap
   keeps the last tab whole on a 320px screen (iPhone Display Zoom) instead of clipping it. */
.re-tabbar-float{position:absolute;z-index:30;
  left:50%;right:auto;transform:translateX(-50%);
  bottom:calc(var(--re-tabbar-inset) + env(safe-area-inset-bottom));
  --_bar-pad:8px;   /* end padding: the pill's radius eats into the first/last item, so give them optical room */
  --_bar-edge:2px;  /* the 1px border on both ends (border-box) */
  --_item-cap:calc((100% - var(--re-tabbar-inset) * 2 - var(--_bar-pad) * 2 - var(--_bar-edge)) / var(--re-tabbar-count, 4));
  width:calc(min(var(--re-tabbar-item-w), var(--_item-cap)) * var(--re-tabbar-count, 4) + var(--_bar-pad) * 2 + var(--_bar-edge));
  max-width:calc(100% - var(--re-tabbar-inset) * 2);
  min-height:var(--re-tabbar-h);padding:0 var(--_bar-pad);
  border:1px solid var(--border-elevated);border-radius:var(--radius-full);   /* v6 V6-10 — floating surface, not a divider */
  box-shadow:var(--shadow-lg);overflow:hidden}
/* v6 V6-9 — inside an app shell the island is FIXED, not absolute. re-app is 100dvh, so the
   two coincide at rest; they diverge when the document rubber-bands on iOS, where an absolute
   bar rides along with the document while a fixed sibling (a consumer's prompt dock) stays
   put — two bars on the same line behaving differently. Deliberately scoped to re-app: a
   standalone island (the phone-frame demo on the docs site) must stay inside its wrapper,
   and a fixed element would escape to the nearest transform/filter/contain ancestor instead. */
.re-app .re-tabbar-float{position:fixed}
/* Items divide the computed bar width equally. This is what removes the WebKit dependency:
   with the bar sized from a token, nothing has to resolve an intrinsic contribution, so
   flex-basis is never consulted during sizing. Each item still ends up at exactly the
   item width (bar width / count), and shrinks with the cap on a narrow screen. */
.re-tabbar-float .re-tabbar-item{flex:1 1 0;min-width:0;min-height:var(--re-tabbar-h)}
/* Content clearance: the island overlays, so the scroll area needs room for
   bar + both insets. One declaration, and the value collapses to 0 on desktop where
   an re-app hides the bar. */
.re-shell-body:has(> .re-tabbar-float){--_tabbar-clear:calc(var(--re-tabbar-h) + var(--re-tabbar-inset) * 2 + env(safe-area-inset-bottom))}
@media (min-width:1024px){.re-app .re-shell-body:has(> .re-tabbar-float){--_tabbar-clear:0px}}
.re-shell-body:has(> .re-tabbar-float) > .re-main:not(.re-main-fill){padding-bottom:max(var(--_tabbar-clear),calc(var(--re-main-end) + env(safe-area-inset-bottom)),calc(var(--re-main-clear,0px) + 16px))}
.re-shell-body:has(> .re-tabbar-float) > .re-main-fill,.re-shell-body:has(> .re-tabbar-float) > .re-main:has(> .re-board-fill){padding-bottom:var(--_tabbar-clear)}

/* ── re-fab — floating primary action ─────────────────────────────── */
.re-fab{position:fixed;right:16px;bottom:var(--_fab-bottom,calc(16px + env(safe-area-inset-bottom)));z-index:300;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--re-fab-h);min-width:var(--re-fab-h);padding:0 20px;border-radius:var(--radius-full);
  background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));border:none;box-shadow:var(--shadow-lg);
  font-family:inherit;font-size:calc(0.9375rem * var(--font-scale));font-weight:600;text-decoration:none;cursor:pointer;transition:transform .12s,box-shadow .12s}
.re-fab:hover{box-shadow:var(--shadow-xl,var(--shadow-lg))}
.re-fab:active{transform:scale(.96)}
.re-fab{--re-icon-ratio:1.6}   /* a standalone action button: 15px type, 24px glyph */
.re-fab svg{width:var(--re-icon);height:var(--re-icon)}
.re-fab-icon{padding:0;width:var(--re-fab-h)}            /* icon-only round */
/* A FAB is MOBILE chrome, and the DS already has an idiom for that — re-tabbar is
   `display:none` inside re-app and `display:flex` again under 1023px (`:2135-2136`). re-fab
   had no viewport rule at all, so hiding it on desktop meant every consumer writing the same
   media query: seven projects, seven chances to pick a different threshold. The number is not
   new either — this component's own block already uses 1023 for the FAB's bottom offset.
   The desktop counterpart is already built: re-ph-actions turns the header into
   `minmax(0,1fr) auto` and centres the actions in column 2, which is where a primary "new …"
   belongs on a wide screen. So the two halves are one decision and the DS holds both.
   ⚠ Scoped to `.re-app`, like the tabbar: a FAB on a plain page (a docs demo, a landing) is
   not app chrome and keeps working. A consumer who genuinely wants one on desktop overrides
   `display` — their unlayered rule beats this. (labs §V27-1) */
/* The new shell (re-shell + re-sidebar, O2) carries no .re-app, so the FAB showed on desktop with its 88px of
   clearance (plan, 1280, v0.80.0). Same rule for both shells. */
.re-app .re-fab,.re-shell:has(> .re-sidebar) .re-fab{display:none}
@media (max-width:1023px){.re-app .re-fab,.re-shell:has(> .re-sidebar) .re-fab{display:inline-flex}}
/* v2 — clear the bottom bar ONLY when it is actually visible. :has(.re-tabbar) matches
   even while the tabbar is display:none on web, which lifted the FAB off the bottom with
   no bar present. Scope the lift to the tabbar's own breakpoint so on web the FAB keeps
   its symmetric 16px inset (bottom = right); on phones it sits 16px above the 52px bar. */
@media (max-width:1023px){
  .re-app:has(.re-tabbar){--_fab-bottom:calc(var(--re-tabbar-h) + 16px + env(safe-area-inset-bottom))}        /* flush bar + 16px gap */
  .re-app:has(.re-tabbar-float){--_fab-bottom:calc(var(--re-tabbar-h) + var(--re-tabbar-inset) * 2 + 16px + env(safe-area-inset-bottom))}  /* island: clear the bar AND its inset */
}
/* v7 §V7-9 — content clearance for the FAB. The tabbar had this and the FAB didn't, so the
   last row of a list sat UNDER the FAB, flush with the screen edge. The clearance is derived
   from the FAB's own --_fab-bottom and --re-fab-h, so it follows the button instead of
   restating its arithmetic: a consumer writing 56+16+16 by hand breaks silently when the
   DS changes the size. --_fab-bottom already accounts for a tabbar and already contains the
   safe-area inset, so this rule must NOT add env() again (v7 §V7-11) — and because it comes
   after the tabbar's own clearance rule, a shell with both gets the larger value. */
.re-shell-body:has(.re-fab){--_fab-clear:calc(var(--_fab-bottom,calc(16px + env(safe-area-inset-bottom))) + var(--re-fab-h) + 16px)}
/* …and collapse it where the FAB is hidden. `:has()` asks whether the element is IN THE
   TREE, and `display:none` does not remove it from the tree — so hiding the FAB above
   left 88px of padding under the last row on desktop with no button present (measured:
   1024px 88px, 390px 146px = 74+56+16). Reported by a consumer whose count of local
   escapes would have gone 0 → 2. It is the same over-match the comment 15 lines up already
   describes for --_fab-bottom, and the tabbar-float clearance already answers it the same
   way (`--_tabbar-clear:0px` at :2202) — so the shape was in the file twice before this
   rule needed it a third time. env() and not 0: a desktop shell with a hidden FAB should
   behave like one with no bottom furniture at all, which is what :1375 gives. (labs §V27-1b) */
@media (min-width:1024px){.re-app .re-shell-body:has(.re-fab),.re-shell:has(> .re-sidebar) > .re-shell-body:has(.re-fab){--_fab-clear:env(safe-area-inset-bottom)}}
.re-shell-body:has(.re-fab) > .re-main:not(.re-main-fill){padding-bottom:max(var(--_fab-clear),calc(var(--re-main-end) + env(safe-area-inset-bottom)),calc(var(--re-main-clear,0px) + 16px))}
/* A fill screen keeps exactly the FAB clearance it always had (no end room: it is a height chain). */
.re-shell-body:has(.re-fab) > .re-main-fill,.re-shell-body:has(.re-fab) > .re-main:has(> .re-board-fill){padding-bottom:var(--_fab-clear)}

/* ── re-sheet — bottom sheet on phones, centered modal ≥768px ──────── */
/* MARKUP CONTRACT (v2 §D): `.open` goes on BOTH the .re-sheet AND its .re-sheet-backdrop
   (the reSheet() helper toggles both, linked by data-for="<id>"). Missing it on one gives
   the most expensive failure mode: overlay with no panel, no console error, no missing rule. */
.re-sheet-backdrop{position:fixed;inset:0;z-index:400;background:color-mix(in srgb,var(--foreground) 32%,transparent);opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s}
.re-sheet-backdrop.open{opacity:1;visibility:visible}
.re-sheet{position:fixed;left:0;right:0;bottom:0;z-index:401;display:flex;flex-direction:column;max-height:92dvh;
  background:var(--card);border-top-left-radius:var(--radius-surface);border-top-right-radius:var(--radius-surface);box-shadow:var(--shadow-lg);
  padding-bottom:env(safe-area-inset-bottom);transform:translateY(100%);transition:transform .26s cubic-bezier(.32,.72,0,1)}
.re-sheet.open{transform:translateY(0)}
.re-sheet-grip{width:36px;height:4px;flex-shrink:0;border-radius:999px;background:var(--border);margin:8px auto 2px}
.re-sheet-header{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:calc(72px * var(--density-mult,1));padding:0 16px;border-bottom:1px solid var(--border);font-size:calc(1.2rem * var(--font-scale));font-weight:700;color:var(--foreground)}
/* v2 A9 — a semantic title (e.g. <h2>) inside the header inherits the header's 1.2rem/700
   instead of the UA's h2 size/margin. Mirrors re-modal-title for the sheet. */
.re-sheet-title{margin:0;font-size:inherit;font-weight:inherit;color:inherit;line-height:1.2}
/* --re-sheet-inset publishes the horizontal padding, because a consumer needs to DERIVE from
   it and had no way to: a horizontally scrolled strip inside a sheet has to bleed out to the
   sheet's edge, and neither re-bleed-x nor re-section-bleed can do it here — both read
   --re-gutter, which is 27px while this padding is 16, so the strip overshot the sheet by
   11px. The consumer's workaround was `margin-inline:-16px`, i.e. our own number copied into
   their stylesheet, silently wrong the day we change it and caught by no gate. (labs §V21-1)
   ⚠ The value is unchanged on purpose. Whether a sheet's text should sit on --re-box-inset
   like every other box (18px at standard, so 16 -> 18) is a separate question with a visible
   answer in every sheet; it is not this request. */
.re-sheet-body{--re-sheet-inset:16px;overflow-y:auto;padding:14px var(--re-sheet-inset);display:flex;flex-direction:column;gap:var(--re-space-4)}  /* v2 A11 — section rhythm (like re-empty-state / re-wizard-body); parts carry no margin */
/* re-main-bleed's sheet sibling. Same shape, different token, and it also has to fix the
   SNAP: re-chip-strip pins scroll-padding-inline to --re-gutter (the page column's number),
   so in a bled strip inside a sheet the snap lands on the column edge and skips the padding —
   the first chip stops under the sheet's inset instead of beside it. The DS already measured
   that failure for re-board and wrote it there; this is the same one, one container in.
   Handed over as a custom property rather than as a later, more specific selector: a variable
   wins by inheritance whatever the source order, and this file has been bitten by order
   before (§V7-12, §V15-3). */
.re-sheet-bleed{
  margin-inline:calc(-1 * var(--re-sheet-inset));
  padding-inline:var(--re-sheet-inset);
  scroll-padding-inline:var(--re-sheet-inset);
  --re-strip-scroll-pad:var(--re-sheet-inset);
}
/* v4 V4-3 — with no footer the body IS the last thing before the screen edge: 14px left the
   content against the home bar, so it gets the body's own section gap instead.
   v7 §V7-11 — it must NOT add the safe-area inset: .re-sheet already carries
   padding-bottom:env(safe-area-inset-bottom), and this element is its child, so the two
   padded the same strip twice. Reported from a real iPhone PWA and confirmed by counting
   pixels in the screenshot: 34 (sheet) + 20 (gap) + 34 (body) = 88px of dead space below
   the last row. It only showed up WITHOUT a footer, because with one the body is no longer
   :last-child. NOTE: env(safe-area-inset-*) resolves to 0 in headless Chrome, so no CDP
   measurement can see this class of bug — to test one, substitute the env() for a variable
   and feed it a real inset. */
.re-sheet-body:last-child{padding-bottom:var(--re-space-4)}
.re-sheet-footer{flex-shrink:0;display:flex;align-items:center;gap:8px;min-height:calc(72px * var(--density-mult,1));padding:0 16px;border-top:1px solid var(--border)}
.re-sheet-footer > .re-btn{flex:1}  /* v2 A10 — mobile two-equal-buttons default (single button → full width); replaces inline style="flex:1" */
@media (min-width:768px){
  .re-sheet{left:50%;right:auto;bottom:auto;top:50%;width:min(480px,94vw);max-height:86dvh;border-radius:var(--radius-surface);
    transform:translate(-50%,-46%);opacity:0;visibility:hidden;transition:transform .2s,opacity .2s,visibility .2s}
  .re-sheet.open{transform:translate(-50%,-50%);opacity:1;visibility:visible}
  .re-sheet-grip{display:none}
}
/* re-sheet-confirm — the family's yes/no question (reConfirm; Ersin 2026-10-07: "borderları kaldıralım, close ikonunu da
   kaldıralım … tek tip bir cümle center align … mobilde alt alta, üstte kırmızı buton altta vazgeç"). No header, no title, no ✕,
   no rule lines: one sentence in the middle and two buttons. Escape and the backdrop still close it (reConfirm wires both), so
   losing the ✕ loses no way out. Markup order is Cancel · action (reading and Tab order); on a phone the column is reversed so
   the action sits on top, under the sentence, and Cancel sits nearest the thumb. */
.re-sheet-confirm .re-sheet-body{padding:28px 20px 6px;text-align:center;font-size:calc(.9375rem * var(--font-scale));line-height:1.5;color:var(--foreground);text-wrap:balance}
.re-sheet-confirm .re-sheet-body:last-child{padding-bottom:28px}
.re-sheet-confirm .re-sheet-footer{border-top:0;min-height:0;flex-direction:column-reverse;align-items:stretch;padding:14px 16px 16px}
/* The footer's buttons are flex:1 (two equal halves of a ROW). In this column flex:1 means a 0 height basis, and the buttons
   collapsed to their text (22px at 390, measured); here they keep their own height. */
.re-sheet-confirm .re-sheet-footer > .re-btn{flex:none}
@media (min-width:768px){
  .re-sheet.re-sheet-confirm{width:min(400px,94vw)}
  .re-sheet-confirm .re-sheet-footer{flex-direction:row;padding:14px 20px 20px}
  /* back to equal shares of the row: half each with two buttons, a third each with three (Ersin 2026-10-07: the flex:none
     above, meant for the phone's column, also left-aligned the desktop buttons at their text width) */
  .re-sheet-confirm .re-sheet-footer > .re-btn{flex:1 1 0}
}

/* ── re-row — touch list row (re-table's phone counterpart) ────────── */
/* width:100% because a <button> does not stretch. re-row is display:flex, and a block-level
   flex container with width:auto fills its block parent — except a <button>, whose used width
   stays shrink-to-fit whatever display you give it, and nothing warns you. Measured on
   patterns/row-to-sheet.html: two div rows at 678px and the button row beside them at 298,
   with its chevron stranded mid-list. It went unseen because every documented button row so
   far sat inside .re-row-group (a flex column, which stretches its items) — the bare form,
   which row.mdx now recommends for a row with no interactive child, had never been placed
   directly in a list. 100% is a no-op for the div and grid cases: as a block box and as a
   flex/grid item it already resolves to the same width. */
.re-row{display:flex;align-items:center;gap:12px;width:100%;min-height:56px;padding:10px var(--re-box-inset);background:var(--card);border-bottom:1px solid var(--border);color:var(--foreground);text-decoration:none}
.re-row-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--re-row-body-gap,4px)}  /* v2 A1 — multi-child rows: stack title + subs (single-sub rows render identically) */
/* ── re-row-eyebrow — the qualifier line ABOVE the title ─────────────────────────
   A row that has to say "which one" before it says "what" was pushing that into its trailing
   slot, where it competed with the chevron for the end of the line and squeezed the title
   (labs §V15-4, and §V12-3 before it — the third time this shape has been reported).

   The trailing slot was the wrong home for it, and the giveaway is that the fix people kept
   asking for was a BREAKPOINT: it only looked like a width problem. It is a reading-order
   problem, and it is wrong at every width. "Şart · 2 paket" then "Marshmallow" is how the
   row is read; a qualifier at the far right is read last, if at all.

   So it goes above the title, which is what re-ph-eyebrow already does one level up. The
   trailing then holds only the affordance, and the squeeze is gone rather than deferred to a
   container query. (User's design, 2026-09-04.)

   Order inside it is the reading order: the condition first, then the quantity. */
.re-row-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:var(--re-space-2);min-width:0;
  font-size:calc(0.75rem * var(--font-scale));color:var(--muted-foreground)}
.re-row-title{display:flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:0;overflow-wrap:anywhere;font-size:calc(0.9375rem * var(--font-scale));font-weight:600;color:var(--foreground)}  /* §3.10 — icon/badge baseline */
/* overflow-wrap:anywhere — wrapping is the documented default, and wrapping cannot help an
   UNBREAKABLE token. Measured on `ops@northwind.example` (no spaces, so no wrap opportunity)
   in a 320px viewport: the body was 114px, the text 148px, and the surplus ran straight under
   the trailing badge. Not an overlap the layout caused — the text simply escaped its box.
   `anywhere` lets a long token break, so the row grows a line instead of leaking; `re-truncate`
   is still the opt-in for clipping. */
/* Time at the title line's END and a foot line (Ersin, notes list view, 2026-10-06: "30dk önce bilgisi sağ üst köşede …
   source ve list left'te, tags'ler en sağda"). The title holds its text in a span and the time after it; the time takes the
   line's end and drops to the next line's end when the title needs the room. The foot is the note card's two lines laid
   out as ONE row line: re-note-meta at the start, re-note-tags at the end. */
.re-row-time{margin-left:auto;flex-shrink:0;font-size:calc(0.75rem * var(--font-scale));font-weight:400;color:var(--muted-foreground);white-space:nowrap}
.re-row-foot{display:flex;align-items:center;flex-wrap:wrap;gap:var(--re-space-1) var(--re-space-2);min-width:0;margin-top:2px}
.re-row-foot > .re-note-tags{margin-left:auto}
.re-row-sub{margin:0;overflow-wrap:anywhere;font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}  /* v2 A1 + v8 — margin:0, not merely omitted: the comment said "dropped" but the UA's p-margin was still arriving on a page with no reset (measured 13px). re-row-body's gap owns rhythm); v2 A2 — wraps by default, opt into re-truncate to clip */
/* re-row-sub's leading icon is handled with re-ph-sub's, above — same failure, same fix. */
/* The slot's size is declared on the LIST, not here, and read with a fallback so a standalone
   re-row still works. It was declared on .re-row-lead and that made the knob unsettable where
   it belongs: an element's own declaration beats an inherited one, so `--re-row-lead-h` written
   on .re-row-list did NOTHING (measured: slot 24, body x unchanged) and a consumer had to write
   it on every single lead. A slot whose whole job is that every row's body starts at the same x
   cannot have a per-row knob — that is a drift generator, not an option. */
.re-row-list{--re-row-lead-h:24px}
.re-row-lead{--re-row-lead-icon:min(calc(var(--re-row-lead-h,24px) * .67),18px);
  width:var(--re-row-lead-h,24px);flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-foreground)}
/* Same hole as re-sidebar-toggle, 25 svgs wide: the slot was sized and its icon was not, so an
   svg with no width attribute drew at 24px while the ones that had an attribute drew at 16 or
   18. Three sizes in one slot, which is what the slot exists to prevent. The 18px in the note
   above is about what the slot must ACCOMMODATE (an avatar is 20-24); the glyph is 16. */
.re-row-lead > svg{width:var(--re-row-lead-icon);height:var(--re-row-lead-icon)}  /* v3 V6 — the slot guarantees the BODY's x, not the lead's edge. Measured in one list: glyph
   16.1 insets 4.0 a side, re-avatar-xs (20) insets 2.0, re-avatar-sm (28) OVERHANGS 2.0 — and
   all four bodies land at the same x. The overhang is a choice, not an error.
   ⚠ This comment used to say "an 18px icon and a 24px avatar align to the same width". Nothing
   in the avatar scale is 24 (it is 20/28/36/48), so it described a size that was never built,
   and two consumers read the 28-in-24 case as a defect because of it. */
/* ── re-row-list-lead-lg — the lead is a PORTRAIT, not a glyph ───────────────────
   A row whose lead identifies a person or an app carries a picture, and a picture at one line
   of text (the 24px default) reads as an afterthought next to a two-line body. This variant
   takes the slot to 36 — the default re-avatar — and tightens the body so the text block sits
   against the portrait instead of overhanging it.

   36 is a measured ceiling, not a taste. The lead may grow up to the two-line text block and
   no further: past that the AVATAR sets the row's height and the list's rhythm stops coming
   from its text. The block is 39.9 at compact (the tightest), so 36 clears every density while
   48 does not — measured, re-avatar-lg takes a compact row from 64.9 to 69.0.

   ⚠ Measure a ceiling at the TIGHTEST density. At comfortable the block is 44.5 and 48 fits,
   so a check run at one setting would have approved a size that breaks the other two.

   The glyph does not follow the slot all the way: min(…, 18px) keeps the default's 16.1px
   identical and stops an icon'd row in the same list from drawing a 24.1px glyph next to a
   15px title. A portrait scales with its slot; a glyph is sized by the text it sits with. */
.re-row-list-lead-lg{--re-row-lead-h:36px;--re-row-body-gap:0px}
.re-row-trailing{flex-shrink:0;display:flex;align-items:center;gap:8px;color:var(--muted-foreground);font-size:calc(0.875rem * var(--font-scale))}
/* The DS sizes the icon it hosts. An <svg> with no width/height renders at its natural size,
   and in a slot that can stretch it renders at the SLOT's size — measured after the stacking
   rule below gave the trailing `flex:1 1 100%`, a chevron in a Billing row drew about 180px
   tall. The same class of bug the anatomy page records for unsized icons elsewhere. */
.re-row-trailing > svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
.re-row-tap{cursor:pointer;transition:background .12s}
.re-row-tap:active{background:color-mix(in srgb,var(--foreground) 5%,transparent)}

/* ── A trailing control that cannot fit drops below the text ───────────────────
   labs v12 §V12-3. Measured on a default three-option re-segment with real labels ("Off" /
   "Ask me" / "Always"): the control is 218px, so the row's body gets 74px at 390, 44px at
   360 and 4px at 320 — and the title, which needs 92px, is truncated at ALL THREE. The
   report saw 9px of clipping; with honest labels it is 18-88px.

   ⚠ It reads as clean if you measure it wrong. An ellipsised box reports
   scrollWidth === clientWidth, so the obvious test says "no overflow" for text that is
   visibly cut. The truncation has to be measured against the text's NATURAL width (a Range
   over the contents), which is how the 88px case surfaced.

   The report offered three fixes and the other two lose: a segmented control that WRAPS
   stops reading as one control, and dropping to -sm only buys ~30% while making the labels
   smaller on the smallest screen — backwards. So the trailing control drops to its own line
   and takes the full width, which is what the platform does with a setting whose control
   will not fit beside its label.

   On a CONTAINER query, not a breakpoint: the row's own width is what decides. The same row
   is fine in a wide column and cramped in a narrow side panel at the same viewport, and a
   media query cannot tell those apart. */
.re-row-list{container-type:inline-size}
/* Which trailing controls move: the ones that WOULD be too wide. A two-option segment is
   147px and sits beside its label happily; a three-option one is 218px and does not. CSS
   cannot ask "does this fit", but it can count — a segment with a third child.

   ⚠ Written as three separate selectors because the compact forms were MEASURED and do not
   work:

     :has(> … > .re-segment:has(> :nth-child(3)))          → SyntaxError; :has() inside
                                                              :has() is not allowed
     :has(> … > :is(.re-segment:has(> :nth-child(3)), …))  → parses, matches NOTHING
     :has(> … > :is(.re-segment > :nth-child(3), …))       → parses, matches NOTHING

   The middle two are the dangerous ones: no error, no match, a rule that looks written and
   does nothing. Verified against a real row — 1 match for the flat form, 0 for both
   laundered ones. */
@container (max-width: 400px){
  .re-row:has(> .re-row-trailing > .re-segment > :nth-child(3)),
  .re-row:has(> .re-row-trailing > .re-select),
  .re-row:has(> .re-row-trailing > .re-input){
    flex-wrap:wrap;row-gap:calc(8px * var(--density-mult,1));
    padding-top:calc(10px * var(--density-mult,1));padding-bottom:calc(10px * var(--density-mult,1));
  }
  /* Only the CONTROL takes the full line. The body used to be listed here too, and that
     is what pushed re-row-lead onto a line of its own: a 100%-wide body leaves the 24px
     icon + 12px gap nowhere to go, so a settings row rendered in THREE lines (icon /
     title / control). The body's own `flex:1` already shares line 1 with the lead, and
     the control still wraps because its 100% basis cannot fit beside it. Measured at
     390px (298px of row): body 298 -> 262, row 3 lines -> 2. (labs §V17-1) */
  .re-row:has(> .re-row-trailing > .re-segment > :nth-child(3)) > .re-row-trailing,
  .re-row:has(> .re-row-trailing > .re-select) > .re-row-trailing,
  .re-row:has(> .re-row-trailing > .re-input) > .re-row-trailing{flex:1 1 100%}
  /* full width once it is on its own line — a 218px control floating left of empty space
     reads as a mistake */
  .re-row-trailing > .re-segment:has(> :nth-child(3)){width:100%;display:grid}
  .re-row-trailing > .re-select,
  .re-row-trailing > .re-input{width:100%}

  /* ── The aligned variant needs its own answer ────────────────────────────────
     re-row-list-align rows are `display:grid` with subgrid, so `flex-wrap` above does
     nothing at all there — measured, the wrap computed as `wrap` and the layout did not
     move: the body's minmax(0,1fr) collapsed to 23px and the title lost 28-49px.

     And the count gate is wrong here for a second reason: the trailing column is shared, so
     it is as wide as the WIDEST control in the list. A two-option segment gets stretched to
     213px along with the rest — measured, "Language" (TR/EN) was clipped by 49px despite
     having nothing wide in it.

     So the whole aligned group stacks together, keyed off the list rather than the row:
     they are one alignment group, and half of them stacking would defeat the point. */
  /* ⚠ This used to collapse the LIST's template here — `.re-row-list-align:has(…){grid-
     template-columns:minmax(0,1fr)}` — and that rule could never apply, because the list IS
     the container: `container-type` is on .re-row-list and .re-row-list-align is the same
     element. A container query styles a container's DESCENDANTS; the element itself is out
     of scope, so the rule sat inside this block reading as live and doing nothing.

     The selector was not the problem and that is what made it hard to see — the list really
     does match it (`list.matches(…)` is true, the segment really has three children). The
     SCOPE was. So the sibling rule below worked, the row went `display:flex`, and the
     template stayed three columns underneath it.

     The symptom is a row narrower than its own list, i.e. a bottom border that stops short.
     Measured at 366px, three-option segments: template `351.8 / 12.2 / 0`, row 12.2px short
     of the list. With the row spanning every track instead: `0 / 364 / 0`, short by **0**.

     ⚠ And it was there before any of this was noticed, because at ordinary widths track 1
     eats everything and the two empty tracks measure 0. A consumer only saw it after
     `re-section-bleed` made the list wider than its rows' max-content. Bleed is the
     REVEALER, not the cause — any layout that widens the list does the same.

     Driving it from the ROW is also the smaller statement: the row is a descendant, so it is
     in scope by construction, and one declaration replaces a rule that had to restate the
     three `:has()` conditions. */
  .re-row-list-align:has(.re-segment > :nth-child(3)) > .re-row,
  .re-row-list-align:has(.re-select) > .re-row,
  .re-row-list-align:has(.re-input) > .re-row{
    display:flex;flex-wrap:wrap;grid-column:1 / -1;
    row-gap:calc(8px * var(--density-mult,1));
    padding-top:calc(10px * var(--density-mult,1));padding-bottom:calc(10px * var(--density-mult,1));
  }
  /* The body leaves the grid column but keeps its own `flex:1` — it must NOT take the full
     line. That half of §V17-1 was fixed for a plain re-row-list and missed here, and this is
     the variant the report itself was using: measured at 390px, the aligned row put the 24px
     re-row-lead on a line of ITS OWN and stood at 105.6px (three lines) while the plain row
     next to it was 81.5px (two). Same cause, same fix — full width belongs to the CONTROL.
     (labs §V17-1, second site) */
  .re-row-list-align:has(.re-segment > :nth-child(3)) > .re-row > .re-row-body,
  .re-row-list-align:has(.re-select) > .re-row > .re-row-body,
  .re-row-list-align:has(.re-input) > .re-row > .re-row-body{grid-column:auto}
  .re-row-list-align:has(.re-segment > :nth-child(3)) > .re-row > .re-row-trailing,
  .re-row-list-align:has(.re-select) > .re-row > .re-row-trailing,
  .re-row-list-align:has(.re-input) > .re-row > .re-row-trailing{
    flex:1 1 100%;grid-column:auto;justify-content:stretch;
  }
  .re-row-list-align > .re-row > .re-row-trailing > .re-segment{width:100%}
  /* Only a row that HAS a wide control gives it the full line. The rule above stretched every
     row's trailing in the group, and a small one, a switch or a count + chevron, then sat at
     the START of its own line: measured at 390px, the switch's right edge at 57 and the
     chevron's at 52 while the segments ended at 371 (contacts, S24-10, engineer 2026-10-01).
     Stacking is the group's decision; what stacks is the control that would not fit. The
     others keep line 1 and the row's end, which is where a plain re-row-list leaves them. */
  .re-row-list-align:has(.re-segment > :nth-child(3)) > .re-row:not(:has(> .re-row-trailing > :is(.re-segment, .re-select, .re-input))) > .re-row-trailing,
  .re-row-list-align:has(.re-select) > .re-row:not(:has(> .re-row-trailing > :is(.re-segment, .re-select, .re-input))) > .re-row-trailing,
  .re-row-list-align:has(.re-input) > .re-row:not(:has(> .re-row-trailing > :is(.re-segment, .re-select, .re-input))) > .re-row-trailing{
    flex:0 0 auto;margin-left:auto;justify-content:flex-end;
  }
}

/* A second, tighter step: under 300px of row width even a SMALL trailing costs too much.
   Measured with a badge + a … menu (108px together) and `ops@northwind.example` as the
   subline: at 280px the body is 74px, so the email breaks over three lines and the row grows
   to 128px; at 240px, five lines and 189px. Nothing is hidden — overflow-wrap sees to that —
   but a five-line row is not a row. Stacked, the body gets the full width back and the email
   fits on one line again.

   No content gate here, unlike the 400px step: at this width the question is not *which*
   control it is, it is that there is one at all.

   250px, not 300, and the difference was measured: at a 320px VIEWPORT the row is 272px wide,
   the email wraps to two lines and the row is 86px — perfectly fine, and stacking it there
   made it 104px, i.e. WORSE. The threshold belongs where the inline version actually breaks
   down, which is where the subline needs a third line: 232px (a 280px viewport). 320px is the
   real device floor, so what this really covers is a row inside a narrow panel — the case a
   media query could never have seen. */
@container (max-width: 250px){
  /* not EVERY trailing: a 16px chevron costs nothing and a row that stacks to put one on its
     own line reads as broken — measured, that is exactly what the first version did to a
     Billing row. Gated on the things that take real width. (Simple class selectors inside
     :is() do match; the forms that silently do not are documented at the 400px step.) */
  .re-row:has(> .re-row-trailing > :is(.re-badge, .re-row-menu, .re-select, .re-input, .re-chip)),
  .re-row:has(> .re-row-trailing > .re-dropdown > .re-row-menu),
  .re-row:has(> .re-row-trailing > .re-segment){
    flex-wrap:wrap;row-gap:calc(6px * var(--density-mult,1));
    padding-top:calc(10px * var(--density-mult,1));padding-bottom:calc(10px * var(--density-mult,1));
  }
  .re-row:has(> .re-row-trailing > :is(.re-badge, .re-row-menu, .re-select, .re-input, .re-chip)) > .re-row-body,
  .re-row:has(> .re-row-trailing > :is(.re-badge, .re-row-menu, .re-select, .re-input, .re-chip)) > .re-row-trailing,
  .re-row:has(> .re-row-trailing > .re-dropdown > .re-row-menu) > .re-row-body,
  .re-row:has(> .re-row-trailing > .re-dropdown > .re-row-menu) > .re-row-trailing,
  .re-row:has(> .re-row-trailing > .re-segment) > .re-row-body,
  .re-row:has(> .re-row-trailing > .re-segment) > .re-row-trailing{flex:1 1 100%;justify-content:flex-start}
}

/* Two TEXT buttons at the end of a row (A-04: two actions may stay visible). Neither step above
   sees them — both gate on badge/menu/select/input/segment — and buttons are `white-space:nowrap`,
   so the body paid for all of it: plan measured "İndir" + "Calendar'a ekle" at 390px and the body
   was 13px, the title broke letter by letter (MC2-3a, 2026-10-06; verify-row-buttons).

   No width threshold here, on purpose: the body is given a basis (9rem, a short title on one line)
   and the row may wrap, so the buttons go down exactly when lead + 9rem + buttons stops fitting —
   whatever the labels are. A container query would have to guess the buttons' width; this asks
   the layout. On its own line the trailing keeps the row's END (margin-left:auto), where the eye
   left it, unlike the control steps above that take the full line from the start.
   Icon buttons are out: a pair of them is ~76px and fits beside any title (plan team.js, split
   settle.js); two icon actions are an A-04 question (`…` menu), not a wrapping one. */
.re-row:has(> .re-row-trailing > .re-btn:not(.re-btn-icon) + .re-btn:not(.re-btn-icon)){flex-wrap:wrap;row-gap:calc(8px * var(--density-mult,1))}
.re-row:has(> .re-row-trailing > .re-btn:not(.re-btn-icon) + .re-btn:not(.re-btn-icon)) > .re-row-body{flex:1 1 9rem}
/* and when the pair is wider than the row itself (320px viewport: 6–9px over), the second button
   goes under the first, both still at the end */
.re-row:has(> .re-row-trailing > .re-btn:not(.re-btn-icon) + .re-btn:not(.re-btn-icon)) > .re-row-trailing{margin-left:auto;max-width:100%;flex-wrap:wrap;justify-content:flex-end}

/* ── The end of a row: what goes there, in what order ─────────────────────────
   labs v13 §V13-4 and §V13-5 are one request. Two of their rules changed and both landed in
   the same 8px slot: `A-04` — a row with two or more actions gets a `…` menu, not a pair of
   icons — and `A-13` — the write-tier badge moved off the section label and onto the account
   row, because the tier belongs to the account and not to the section. `re-dropdown-menu`
   and `re-badge` both existed; how they sit HERE did not.

   The order is not arbitrary, it is left-to-right by kind:

     [ state ]  [ value ]  [ … actions ]  [ › navigate ]
       badge      count       menu           chevron

   State and value are things to READ; the menu and the chevron are things to DO. Reading
   before doing means the eye finishes the row before it reaches a control, and it means the
   two do-things are adjacent instead of separated by a badge.

   A row rarely holds both a menu and a chevron. When it does, the chevron is last, because
   it is the row's own action (tap the row) while the menu is a secondary one — putting the
   menu outside the chevron would read as "this menu belongs to the next screen".

   ⚠ The menu can only open here because re-row-list stopped clipping (v14 §V14-1). Before
   that fix an absolutely-positioned panel inside a row was cut off by the list's
   overflow:hidden, and no z-index could help — clipping is independent of stacking. */
/* The two numbers live on the ROW, not on the button, and that placement is the fix for a
   bug this exact rule shipped with for one measurement: the optical pull below targets the
   button's WRAPPER (an re-dropdown), and a custom property is substituted at the element
   that declares it — never inherited upward. Declared on the button, `var(--re-row-menu-size)`
   was invalid on the wrapper, the calc() was invalid with it, and the whole declaration was
   dropped. Measured: margin-right 0px, dots 8px inside the row's text edge. */
.re-row{--re-row-menu-size:var(--control-h-sm);--re-row-menu-icon:16px}
.re-row-menu{
  /* the fallbacks are for a trigger used outside a row; inside one the row's values win */
  display:inline-flex;align-items:center;justify-content:center;
  width:var(--re-row-menu-size,var(--control-h-sm));height:var(--re-row-menu-size,var(--control-h-sm));
  flex-shrink:0;padding:0;
  border:0;background:transparent;border-radius:var(--radius-control);
  color:var(--muted-foreground);cursor:pointer;font-family:inherit;
}
.re-row-menu > svg{width:var(--re-row-menu-icon,16px);height:var(--re-row-menu-icon,16px)}
.re-row-menu:hover{background:color-mix(in oklch,var(--foreground) 7%,transparent);color:var(--foreground)}
.re-row-menu[aria-expanded="true"]{background:color-mix(in oklch,var(--foreground) 9%,transparent);color:var(--foreground)}
/* Optically aligned at the row's edge, for the same reason the top bar's end buttons are:
   the button's box is wider than its glyph, so a button flush to the row's text inset draws
   its dots further in than the text above them. The box overhangs, the glyph lands on the
   inset, and the hover fill stays centred. Both numbers are the DS's, so the pull is exact. */
/* the trigger is usually wrapped in an re-dropdown, so it is the DROPDOWN that is the last
   child — measured, the pull silently did nothing until this selector covered that case:
   the dots sat 8px inside the row's text edge instead of on it. */
.re-row-trailing > .re-row-menu:last-child,
.re-row > .re-row-menu:last-child,
.re-row-trailing > .re-dropdown:last-child,
.re-row > .re-dropdown:last-child:has(> .re-row-menu){
  margin-right:calc(-1 * (var(--re-row-menu-size) - var(--re-row-menu-icon)) / 2)}
/* the panel hangs from the trigger and opens toward the row's inside — a right-aligned menu
   at the right edge of a narrow row would otherwise leave the screen */
.re-row-trailing > .re-dropdown > .re-dropdown-menu{left:auto;right:0}
/* A menu inside a tappable row must not fire the row. The DS cannot stop event propagation
   from CSS, so this is the contract, written where it will be read: put the trigger in an
   re-dropdown and call event.stopPropagation() in its handler. */
.re-row-done .re-row-title{text-decoration:line-through;color:var(--muted-foreground)}
/* The flag bar lives INSIDE the row's text inset, so flagging a row must not move its text:
   the border takes its width out of the padding, it does not add to it.
   `padding-left: 11px` was that subtraction written as a constant — correct when
   --re-box-inset was 14px, and silently wrong the moment the inset became 18. Measured on the
   app layouts: a flagged row's check button sat 4px left of its neighbours'. Derive it. */
.re-row-flag{--re-row-flag-w:3px;
  border-left:var(--re-row-flag-w) solid var(--_solid,var(--warning));
  padding-left:calc(var(--re-box-inset) - var(--re-row-flag-w))}
/* v2 A3 — list frame for flush rows (re-table's re-table-shell counterpart). overflow:hidden
   so row backgrounds don't square the corners; owns the last row's border reset so markup
   need not write style="border-bottom:0". */
/* v14 §V14-1 — no `overflow:hidden` here. It was clipping the corners for free, but the
   other end of that trade was measured: NOTHING inside a row list could leave it. A
   re-tooltip balloon (`bottom: calc(100% + 6px)`) was cut off in two consumers, and
   z-index cannot help — overflow clipping is independent of stacking, so the rule was
   right and the assumption "z-index:300 is enough" was wrong. The same applied to every
   popover, dropdown and ::after bubble in a row.
   So the rounding moves to the rows that sit at the ends. `inherit` would leave a hairline
   at the corner (the row is inset by the list's 1px border), hence the -1px. */
.re-row-list{border:1px solid var(--border-surface);border-radius:var(--radius-surface);background:var(--card);box-shadow:var(--shadow-sm)}
.re-row-list{--_rl-r:calc(var(--radius-surface) - 1px)}
/* ⚠ `of :not([hidden])` — the end rows are the end VISIBLE rows.
   A filtered list hides rows with the `hidden` attribute (that is what reFnApply sets), and
   :first-child / :last-child kept matching the hidden ones. Measured on the filter demo: with
   the second row hidden, the first row still had square bottom corners AND kept its divider,
   so a one-row list showed a stray line across it and a clipped corner; hiding the FIRST row
   instead left the visible row with square top corners inside a rounded frame.
   The list cannot clip its overflow to hide this — that was removed in v14 §V14-1 because it
   cut off tooltips and row menus — so the rounding lives on the rows and has to know which
   rows a reader can actually see. */
/* the FIRST visible row — which may be a group's head one level down */
.re-row-list > :nth-child(1 of :not([hidden])),
.re-row-list > .re-row-group:nth-child(1 of :not([hidden])) > .re-row{border-top-left-radius:var(--_rl-r);border-top-right-radius:var(--_rl-r)}
/* the LAST visible row — a plain row, a closed group's head, or the last row of an open body */
.re-row-list > :nth-last-child(1 of :not([hidden])),
.re-row-list > .re-row-group:nth-last-child(1 of :not([hidden])):not(.open) > .re-row,
.re-row-list > .re-row-group:nth-last-child(1 of :not([hidden])).open > .re-row-group-body,
.re-row-list > .re-row-group:nth-last-child(1 of :not([hidden])).open > .re-row-group-body > .re-row:nth-last-child(1 of :not([hidden])){border-bottom-left-radius:var(--_rl-r);border-bottom-right-radius:var(--_rl-r)}  /* v6 V6-1 — the same --shadow-sm as re-card. The two get used in the SAME slot (a row list
     or a stacked card, depending on the content) and shared everything but the shadow, so one
     floated and the other looked sunk into the page. That split was residue from being written
     at different times, not a hierarchy. For a box that should read as flat, say so with
     re-card-flat rather than relying on a component's history. */
.re-row-list > .re-row:nth-last-child(1 of :not([hidden])){border-bottom:0}
/* v14 §V14-2 — opt-in: trailing controls in the same list share ONE column.
   The contract the DS already kept was "a segment's own items are equal", and it held
   (56/56/56 and 58/58/58). The one it never stated was "controls at the end of a row start
   in the same column as the ones above them" — so two segments in one list came out 179px
   and 185px, their left edges 6px apart, and a third with two options 107px away.
   A fixed width would have been a magic number that goes wrong the day a label changes, so
   the column is genuinely SHARED: the list becomes the grid and each row borrows its
   columns with `subgrid`. Column 3 then measures the widest trailing content in the whole
   list, and column 1 does the same for the leads.
   Placement is explicit because it has to be: a row may have no lead, and without
   `grid-column` its body would fall into the lead's column.
   ⚠ Does not compose with re-row-group — a group's rows are grandchildren of the list, so
   they are outside this subgrid chain. Use it on flat lists. */
.re-row-list-align{display:grid;grid-template-columns:auto minmax(0,1fr) auto}
.re-row-list-align > .re-row{display:grid;grid-template-columns:subgrid;grid-column:1 / -1;align-items:center}
.re-row-list-align > .re-row > .re-row-lead{grid-column:1}
.re-row-list-align > .re-row > .re-row-body{grid-column:2;min-width:0}
.re-row-list-align > .re-row > .re-row-trailing{grid-column:3;justify-self:stretch;display:flex;justify-content:flex-end}
/* sharing the column aligns the trailing SLOTS, not what is inside them — a re-segment is
   inline-grid, so it kept shrinking to its own content and only the right edges lined up
   (measured: 179 / 185 / 78 with the left edges 107px apart). Inside an aligned list a
   segment fills its slot, which is what puts every left edge on one x. */
.re-row-list-align > .re-row > .re-row-trailing > .re-segment{display:grid;width:100%}

/* v11 §V11-1 — a row that expands, inside the list's own rhythm. The ask was explicit:
   express child-ness with BACKGROUND, not indentation. The head tints --tint-soft, the children
   --tint-softer, and nothing shifts sideways: re-row-lead is a fixed 24px and re-row's padding
   is fixed, so a child starts at the same x as its parent by construction (measured: 0px).
   NOT a box. Nesting an re-row-list, the only tool a consumer had, brings a border, a
   --radius-surface, a --shadow-sm and a --card ground -- the embedded-panel look the design
   rejects, and re-accordion has the same problem for the same reason. This is a group of
   rows, so it inherits the list's borders and adds only a tint. */
.re-row-group{display:flex;flex-direction:column}
/* Closed, a group is just a row — it sits in the list without announcing itself, and the tint
   is what OPENING does. (First version tinted it always, which made a collapsed group look
   selected.) */
.re-row-group > .re-row{background:var(--card)}
.re-row-group.open > .re-row{background:var(--tint-soft)}  /* --tint-soft, not --muted: a group belongs to the list, and --muted reads like a panel header. */
.re-row-group-body{display:flex;flex-direction:column;background:var(--tint-softer)}
.re-row-group-body > .re-row{background:transparent}
/* Lines INSIDE an open group are lighter than the list's own. Two things follow: the group
   reads as one block with an outline of its own rather than as more list rows, and a full-weight
   line stops competing with the tint it sits between. The group's LAST line stays full weight —
   that one is the boundary to whatever comes next, which is exactly what must not soften.
   Mixed toward transparent, not toward a colour: a two-colour mix would drag the hue (the
   --re-note-hue lesson), while a transparent-ended one only thins. */
.re-row-group.open > .re-row,
.re-row-group-body > .re-row{border-bottom-color:color-mix(in oklab,var(--border) 55%,transparent)}
.re-row-group-body > .re-row:last-child{border-bottom-color:var(--border)}
/* The group's last row drops its border only when the GROUP is also the list's last child —
   the list draws its own edge there. Anywhere else the line has to stay, or the next row
   reads as one more child of this group: two open groups in a row came out as dark/light/
   dark/light bands with no boundary between them, so the second group's HEAD looked like a
   child of the first. (The unconditional version was the bug.) */
.re-row-list > .re-row-group:last-child > .re-row-group-body > .re-row:last-child,
.re-row-list > .re-row-group:last-child:not(.open) > .re-row{border-bottom:0}  /* Second selector: a CLOSED group as the list's last child. The list's own
     `> .re-row:last-child{border-bottom:0}` cannot reach it — :last-child there is the
     re-row-group, not the re-row inside it — so the head kept its line and it doubled up
     with the list's bottom edge. Visible only while collapsed, which is why it read as a
     border that appears and disappears with the toggle. */
.re-row-group:not(.open) > .re-row-group-body{display:none}
.re-row-group-caret{flex-shrink:0;width:14px;height:14px;color:var(--muted-foreground);transition:transform .15s}
/* The caret goes LAST, at the trailing edge — not in re-row-lead and not before it. The left
   side belongs to the content: an account row already puts its own icon there and stacks a
   title, a subline and a sync note. Two earlier attempts were wrong in the same direction:
   in the lead it evicted the content's icon, and before the lead it pushed the head's text
   26px right of every other row in the list. At the trailing edge nothing shifts (measured:
   a group head is positionally identical to a plain row) and the caret's PRESENCE is what
   says the row has children — which is the signal a leading position could never carry,
   because there the slot has to exist whether or not anything expands. */
.re-row-group.open > .re-row .re-row-group-caret{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.re-row-group-caret{transition:none}}

/* ── re-stat — metric box + auto-fit grid ─────────────────────────── */
/* --re-box-inset horizontally, not 16px: a stat card sits under an re-section-label, and the
   label's gutter IS this inset — so a hardcoded 16 put the card's text 2px left of the label
   above it and of the row list below it. Three boxes in one column, one text axis. */
.re-stat{display:flex;flex-direction:column;gap:3px;padding:14px var(--re-box-inset);background:var(--card);border:1px solid var(--border-surface);border-radius:var(--radius-surface)}
.re-stat-label{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--muted-foreground)}
.re-stat-value{font-size:calc(1.5rem * var(--font-scale));font-weight:700;line-height:1.1;color:var(--foreground);font-variant-numeric:tabular-nums}
.re-stat-sub{font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-stat-delta{display:inline-flex;align-items:center;gap:3px;font-size:calc(0.8125rem * var(--font-scale));font-weight:600}
.re-stat-delta.up{color:var(--success)}
.re-stat-delta.down{color:var(--danger)}
.re-stat-sm .re-stat-value{font-size:calc(1.125rem * var(--font-scale))}
.re-stat-lg .re-stat-value{font-size:calc(2rem * var(--font-scale))}
.re-stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}  /* v2 A6 — 150px base so a money re-stat-value (e.g. "6.335,50 ₺") doesn't wrap in the 481–560px band */
/* On phones stack to one column instead of an uneven 2+1 for a three-up set */
@media (max-width:480px){.re-stat-grid{grid-template-columns:1fr}}

/* ── re-check-btn — 26px touch check (on / partial) ───────────────── */
.re-check-btn{--re-check-h:26px;--re-check-icon:calc(var(--re-check-h) * .58);
  width:var(--re-check-h);height:var(--re-check-h);flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;padding:0;
  background:none;border:2px solid var(--border);border-radius:50%;color:transparent;cursor:pointer;transition:background .12s,border-color .12s,color .12s}
.re-check-btn svg{width:var(--re-check-icon);height:var(--re-check-icon)}
.re-check-btn.on{background:var(--_solid,var(--primary));border-color:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground))}   /* v4 V4-4 — reads the dictionary so re-c-{role} works ("done" green vs "selected" primary); default unchanged */
.re-check-btn.partial{border-color:var(--_solid,var(--primary));background:color-mix(in srgb,var(--_solid,var(--primary)) 16%,transparent);color:var(--_solid,var(--primary))}
.re-check-btn:hover{border-color:var(--_solid,var(--primary))}

/* ── re-person — selectable avatar chip, per-person --hue ──────────── */
/* The dot's size and the vertical padding are tokens because the chip's HEIGHT is derived
   from them. Without that, a chip with an avatar and a chip without one are two different
   heights of one component in the same row — measured 26px against 24px at -sm, which is the
   defect re-tag-remove already taught us. min-height, not height, so a wrapped name grows. */
.re-person{--re-person-dot-h:24px;--re-person-pad-y:4px;
  display:inline-flex;align-items:center;gap:7px;
  padding:var(--re-person-pad-y) 11px var(--re-person-pad-y) 4px;--hue:250;
  min-height:calc(var(--re-person-dot-h) + 2 * var(--re-person-pad-y) + 2px);
  background:var(--card);border:1px solid var(--border);border-radius:var(--radius-full);color:var(--foreground);
  font-family:inherit;font-size:calc(0.8125rem * var(--font-scale));font-weight:500;cursor:pointer;transition:background .12s,border-color .12s,opacity .12s}
.re-person-dot{--re-person-dot-glyph:.52;
  width:var(--re-person-dot-h);height:var(--re-person-dot-h);flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;font-size:11px;font-weight:700;
  background:oklch(72% 0.12 var(--hue));color:oklch(30% 0.09 var(--hue))}
/* A "+" typed as text is not centred and never will be: it is a glyph on a baseline inside a
   circle, so it sits where the font's metrics put it, which is low and slightly left. The
   review caught it once the chip heights were even and nothing else was moving. So it is an
   icon — lucide/plus.
   Then it STILL read as a hair off, and that one is sub-pixel: a 12.48px icon box and a 24px
   circle are two boxes at a fractional page offset, and the rasteriser snaps each to its own
   device-pixel grid. Measured at 16x: the glyph landed 0.6px low at .52 and 0.3px HIGH at
   .5 — the sign flips with the size, which is the signature of snapping rather than of a
   layout error. At DPR 1 that becomes a whole pixel.
   Fix: give the svg the SAME box as the circle, so the two snap together and cannot disagree,
   and shrink the drawing with a transform instead of the box. A transform is applied after
   layout, about that shared box's centre, at device resolution. */
.re-person-dot > svg{width:100%;height:100%;transform:scale(var(--re-person-dot-glyph))}
.re-person.on{border-color:oklch(62% 0.15 var(--hue));background:color-mix(in srgb,oklch(62% 0.15 var(--hue)) 12%,var(--card))}
/* v7 — the base padding is 4px left / 11px right ON PURPOSE: the avatar hugs the left edge
   and the 11px sits after the NAME. With no avatar (a text-only ghost like "Unassigned") the
   4px left was room for something that isn't there, so the text hugged the edge while 11px
   floated on the right. Symmetric when there is no dot.
   The reverse case — an avatar with no name — cannot be detected in CSS (a text node is
   invisible to :has()), and it shouldn't be: an avatar on its own is re-avatar, not a person
   chip with the label removed. */
.re-person:not(:has(.re-person-dot)){padding-inline:11px}
.re-person-sm:not(:has(.re-person-dot)){padding-inline:9px}
.re-person-ghost{background:none;border-style:dashed;color:var(--muted-foreground)}
.re-person.done{opacity:.5}
.re-person-sm{--re-person-dot-h:20px;--re-person-pad-y:2px;padding:var(--re-person-pad-y) 9px var(--re-person-pad-y) 2px;font-size:calc(0.75rem * var(--font-scale))}
.re-person-sm .re-person-dot{font-size:10px}

/* ── re-chip-strip — horizontal snap strip (compose with re-scroll-x) ─ */
.re-chip-strip{display:flex;gap:8px;scroll-snap-type:x proximity;scroll-padding-inline:var(--re-strip-scroll-pad,var(--re-gutter))}  /* v2 §2.10 — snap respects the bleed gutter so the first chip isn't flush to the edge */
.re-chip-strip > *{flex-shrink:0;scroll-snap-align:start}
/* No scrollbar on a strip. Three consumers (heyre, plan, split) hid it themselves with the same two lines and
   the same reason: a chip strip is short and swiped, and a classic scrollbar takes layout space under it, so
   the strip grew taller the moment its chips overflowed (the re-diagram-bar measurement below, 62 vs 53px).
   The last chip is cut at the edge, which is the affordance; nothing is hidden by hiding the bar. */
.re-chip-strip{scrollbar-width:none}
.re-chip-strip::-webkit-scrollbar{display:none}

/* ── re-hint — one-line inline note (re-alert's light form) ────────── */
.re-hint{margin:0;display:flex;align-items:flex-start;gap:7px;font-size:calc(0.8125rem * var(--font-scale));line-height:1.4;color:var(--muted-foreground)}  /* v2 A12 — top-align icon on wrapping hints; v3 V7 — margin:0 (often written <p class="re-hint">; UA p-margin was adding to the column gap, mirrors re-banner p reset) */
.re-hint svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0;margin-top:2px;color:var(--_text,var(--muted-foreground))}  /* v2 A12 — 2px nudge centers the icon on the 18.2px first line */
/* Outside an re-section-block nothing supplies a gap (re-hint is margin:0), so two of them
   touched — the same defect in the other direction. Positive here, a delta inside the block;
   the block's rule is more specific and wins. */
.re-hint + .re-hint{margin-top:var(--re-hint-gap)}
.re-hint-warning{color:color-mix(in srgb,var(--warning) 72%,var(--foreground))}
.re-hint-warning svg{color:var(--warning)}

/* ── re-section-label — small icon'd uppercase heading ────────────── */
/* The leading slot is RESERVED, not produced by the icon. Measured before: text started at
   x=331 with an icon and x=312 without — 19px apart, so two labels in one column did not
   line up with each other, let alone with anything below them. The icon is now absolutely
   placed inside the slot, so the text starts at --re-label-gutter either way.
   The gutter keeps the icon'd label exactly where it was (13px icon + 6px gap = 19px), so
   nothing that already had an icon moves. */
.re-section-label{position:relative;display:flex;align-items:center;padding-inline:var(--re-label-gutter);margin:8px 0 0;font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted-foreground)}
.re-section-label > svg{position:absolute;left:0;top:50%;transform:translateY(-50%)}
.re-section-label:first-child,.re-section-head:first-child{margin-top:0}
/* v3 V4 — in a gap-managed column (a re-gap-* utility parent) the 8px would double up with
   the column gap, leaving the label floating between two boxes. Let the gap own the spacing
   there; standalone / block-flow use keeps the 8px. :where() keeps specificity at .re-section-label. */
:where([class*="re-gap-"]) > .re-section-label{margin-top:0}
/* v3 V4b + V8 — re-section-head: a heading BLOCK grouping the label, an optional right-aligned
   action, and an optional subline — as ONE gap-column child, so the column gap can't split the
   label from its sub (V8) and the action can't set the section's height (V4b). One bottom margin
   to the content box; children carry none. Grid: row 1 = label | action, row 2 = full-width sub. */
/* The label belongs to the box BELOW it, so the extra space goes ABOVE. Measured before it
   was the other way round: 16px above the label and 24px between the label and its own box,
   which read as the label floating after the previous section. */
.re-section-head{display:grid;grid-template-columns:1fr auto;align-items:center;column-gap:12px;margin:8px 0 0;padding-right:var(--re-label-gutter)}  /* the right action mirrors the left gutter, so both edges of the heading row sit where the box below them does */
.re-section-head > .re-section-label{margin-top:0}
/* The head owns its row's RIGHT edge (padding-right above, so the action lands on the box's
   text edge); the children own the LEFT one, because the label's icon slot is measured from
   their own padding. Both writing it doubles the inset: measured, the sub's text column ended
   at 476.4 instead of 498.2 — 21.8px in from where the box's text ends, at comfortable. So the
   children give the right axis back inside a head, and keep it everywhere else. */
.re-section-head > .re-section-label,.re-section-head > .re-section-label-sub{padding-right:0}
.re-section-head > .re-btn:not(.re-btn-text),.re-section-head > .re-chip{margin-block:-8px;justify-self:end}  /* pull a taller control into the label's band so it doesn't inflate the section height */
.re-section-head > .re-btn-text{justify-self:end;align-self:center}   /* text-sized already: no pull, so nothing overhangs and no hover box can cover the subline */
/* v4 V4-1 — MARKUP CONTRACT: a section-head action is BORDERLESS. Pulling a 32px control into
   the ~17px label band (rule above) makes it overhang ~7.5px below, which leaves a bordered or
   filled button ~2px from the content box — the two edges then read as one. A ghost-style
   control has no edge to crowd, so the overhang is invisible and the section keeps ONE rhythm
   (with a border we'd be back to v3 V4b's two rhythms). Enforced here so re-btn-outline gets
   the right result too; if you need a bordered/filled action, put it INSIDE the box instead. */
.re-section-head > .re-btn:not(.re-btn-text){background:transparent;color:var(--_text,var(--foreground));border-color:transparent;box-shadow:none}
.re-section-head > .re-btn:not(.re-btn-text):hover:not(:disabled){background:color-mix(in oklch,var(--_solid,var(--primary)) 12%,transparent)}
.re-section-label-sub{position:relative;grid-column:1 / -1;margin:2px 0 0;padding-inline:var(--re-label-gutter);display:flex;align-items:flex-start;font-size:calc(0.8125rem * var(--font-scale));line-height:1.4;color:var(--muted-foreground)}  /* the subline is part of the heading — tight to the label, full row. v5 V5-4 — flex + gap gives it re-hint's icon box, so a leading icon stays on the first line when the text wraps. */
.re-section-label-sub > svg{position:absolute;left:0;top:2px;width:var(--re-label-icon);height:var(--re-label-icon);flex-shrink:0}   /* same reserved slot as re-section-label, so both texts start at one x with or without an icon */
:where([class*="re-gap-"]) > .re-section-head{margin-top:0}  /* same gap-column reset as the bare label */
/* ── re-diagram — a pan/zoom frame for a diagram (mermaid, SVG, an <img>) ────────
   The DS does NOT render diagrams: mermaid is a script the consumer loads, and this is the
   FRAME around whatever it produces — a bordered surface with a dot grid, a transformed
   inner layer, and a control cluster. Two reasons it is a component and not a snippet:
   a diagram that overflows its box with no way to move is unreadable, and every consumer
   that hand-rolls the frame picks a different grid, a different button size and a different
   fullscreen mechanism.

   Structure:
     re-diagram              the frame — overflow:hidden, so the pan layer is clipped by it
     └─ re-diagram-pan       the ONLY transformed element (translate + scale); put the
     │                       diagram inside it and leave its transform to the JS
     └─ re-diagram-ctrls     top-right cluster of real re-btn buttons, outside the pan layer
                             so the controls do not zoom with the content

   Fullscreen reuses re-modal-backdrop rather than inventing a second overlay: move the
   frame into an open backdrop and add re-diagram-full. One backdrop, one z-index, one
   Escape handler for the whole DS.

   The grid is painted with a radial-gradient on the frame, NOT on the pan layer — a grid
   that scales with the content stops reading as a ground and starts reading as part of the
   drawing. Retune with --re-diagram-h / --re-diagram-dot. */
/* The frame's height comes from its WIDTH, not from a number. A diagram has no natural size
   of its own, so a fixed height is a guess that goes wrong in both directions: 420px was
   letterboxed in a wide column and, in the window demo, a hand-set 220px squeezed four nodes
   into a strip. 16:9 is the ratio a graph is drawn to and the one a reader expects a canvas
   to have, and --re-diagram-min-h is the floor for a narrow column — below ~569px the ratio
   alone would give less height than the content needs.
   --re-diagram-h still wins when you set it, for the case where the drawing genuinely has a
   height (an image, a fixed-size SVG). Both the window and fullscreen turn the ratio OFF —
   there the height comes from the flex row and the viewport, and an aspect-ratio fighting
   either one is how a canvas ends up shorter than the box it is in. */
.re-diagram{position:relative;height:var(--re-diagram-h,auto);
  aspect-ratio:var(--re-diagram-ratio,16 / 9);min-height:var(--re-diagram-min-h,320px);
  overflow:hidden;
  border:1px solid var(--border);border-radius:var(--radius-surface);background:var(--surface);
  background-image:radial-gradient(circle,color-mix(in oklab,var(--muted-foreground) 28%,transparent) 1px,transparent 1px);
  background-size:var(--re-diagram-dot,20px) var(--re-diagram-dot,20px);
  touch-action:none}   /* the JS owns the gesture; without this the browser scrolls the page mid-pan */
.re-diagram-pan{width:100%;height:100%;display:flex;align-items:center;justify-content:center;
  transform-origin:center center;cursor:grab}
.re-diagram-pan:active{cursor:grabbing}
.re-diagram-pan > *{background:none;border:0;margin:0;padding:0;max-width:none}  /* a mermaid <pre> arrives with all four */
.re-diagram-ctrls{position:absolute;top:var(--re-space-3);right:var(--re-space-3);z-index:2;display:flex;gap:var(--re-space-1)}
/* The controls float OVER the canvas, so they need a ground of their own — a transparent
   button with the graph running under it reads as part of the graph.
   --popover, not --card: the relationship inverts between themes and --card only gets one of
   them right. Measured — canvas (--surface) is 98% in light and 23% in dark; --card is 100%
   and 22.5%, so it is a step brighter in light and a step DARKER in dark. --popover is 100%
   and 25%: one step out of the page in both, which is what "floating above content" means. */
.re-diagram-ctrls > .re-btn{background:var(--popover)}
/* re-btn-outline's hover is a TRANSPARENT tint, which is right on a page and wrong here: it
   replaces the ground the rule above just gave the button, so hovering put the graph back
   underneath. Mix the tint INTO --popover instead of over nothing — the button stays opaque
   and still reads as hovered. */
/* Mixing --primary in tinted these purple, which reads as a selected state rather than a
   hover — a control over a diagram is chrome, and chrome hovers NEUTRAL. --foreground into
   --popover is the DS's own neutral hover (re-row-menu, re-prompt-action): a few tones darker
   in light, a few lighter in dark, and it stays opaque either way. */
.re-diagram-ctrls > .re-btn:hover:not(:disabled){
  background:color-mix(in oklch,var(--foreground) 8%,var(--popover))}
/* fullscreen — the frame fills the SCREEN, minus one uniform gap.
   It used to be `min(1400px, 92vw)` by `min(900px, 88vh)`, and on any display wider than
   1522px the two absolute caps won: measured at 1920x1080, a 1400x900 frame inside a
   1920x1080 screen — 66% of the pixels, with the rest of the screen dimmed and unused. A
   diagram is the one thing in the DS with no natural size of its own, so "fullscreen" should
   mean the screen; capping it in px picked a canvas for the reader.
   One gap on all four sides, so the frame carries the viewport's proportions instead of a
   chosen ratio, and the backdrop's own padding steps aside so the gap has a single owner.
   dvh, not vh: on a phone the browser chrome makes vh taller than what you can see. */
.re-modal-backdrop > .re-diagram-full{
  --re-diagram-full-gap:24px;
  width:calc(100vw - 2 * var(--re-diagram-full-gap));
  height:calc(100dvh - 2 * var(--re-diagram-full-gap));
  --re-diagram-h:auto;aspect-ratio:auto;min-height:0;
  box-shadow:var(--shadow-lg);border-color:var(--border-elevated)}
.re-modal-backdrop:has(> .re-diagram-full){padding:0}
/* an empty frame still has to say so, like every other container in the DS */
.re-diagram-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}

/* ── re-diagram-window — the frame plus the chrome a real map needs ──────────────
   re-diagram is a canvas. A map that a reader has to *work* is a canvas plus four things,
   and a consumer built all four by hand (measured on ai-environment-map): a filter bar, the
   controls moved into that bar, a reading note under the canvas, and a colour key. Worse, it
   forked reDiagramFull into mapDiagramFull for one reason — going fullscreen has to take the
   WINDOW, not the canvas. A canvas that leaves its filters and its key behind on the way to
   fullscreen is less usable large than it was small.

   Structure — the window is a flex column and the canvas is the only part that grows:
     re-diagram-window
     ├─ re-diagram-bar        filters (an re-fn) + re-diagram-ctrls, pushed right
     ├─ re-diagram            the canvas; loses its own border and radius here
     ├─ re-diagram-note       "how to read this" — part of the diagram, not of the page
     └─ re-diagram-legend     the colour key; re-c-{role} on each dot

   Every part is optional and order is not enforced: a window with only a bar is valid, and
   the note can sit above the legend or replace it. */
.re-diagram-window{display:flex;flex-direction:column;min-width:0;
  border:1px solid var(--border);border-radius:var(--radius-surface);background:var(--card);
  box-shadow:var(--shadow-sm);overflow:hidden}
/* The canvas gives up its frame to the window and becomes the part that grows. Nothing else
   in the window may grow — a note that stretches leaves the canvas short. */
/* At rest the canvas takes its ratio height — `flex:1` with `min-height:0` let it shrink
   below the ratio, which is the squeeze itself. In fullscreen it becomes the growing part,
   and there the flex row owns the height so the ratio steps aside. */
.re-diagram-window > .re-diagram{border:0;border-radius:0;flex:none}
.re-diagram-window.re-diagram-full > .re-diagram{flex:1 1 0;min-height:0;
  height:auto;aspect-ratio:auto}
.re-diagram-window > .re-diagram-bar,
.re-diagram-window > .re-diagram-note,
.re-diagram-window > .re-diagram-legend{flex:none}
/* ONE row, never wrapping. With flex-wrap the controls dropped to a second line the moment
   the filters filled the row, and margin-left:auto then pushed them right across an empty
   gap — measured at a 618px window: a 2-row bar, 130px tall, most of it blank. A toolbar
   that grows a row when you add a filter is a toolbar that moves the canvas. So the filters
   get a scrolling slot and the controls stay pinned. */
.re-diagram-bar{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--re-space-2);
  padding:var(--re-space-2) var(--re-box-inset);border-bottom:1px solid var(--border)}
/* The filter slot: whatever strip you put first grows and scrolls instead of pushing the
   controls off. re-chip-strip is the DS's own answer for this and needs no extra class here. */
.re-diagram-bar > .re-chip-strip,
.re-diagram-bar > .re-filter-bar{flex:1;min-width:0;overflow-x:auto;overflow-y:hidden;flex-wrap:nowrap;
  /* No scrollbar gutter: a classic scrollbar takes layout space, so the bar was 62px tall
     when the filters overflowed and 53px when they did not — measured at 1200 / 760 / 500px.
     A toolbar that changes height as you add a filter moves the canvas under the reader,
     which is the thing this one row exists to prevent. The chips are swipeable and the last
     one is cut off at the edge, so nothing is hidden by hiding the bar. */
  scrollbar-width:none;
  /* Fade the trailing edge, because the strip's own edge is a HARD CUT: at rest the last
     chip was sliced through its border a few px from the reset button, which reads as a
     rendering fault rather than "there is more this way". The mask is self-correcting — with
     nothing to scroll the last chip sits left of the fade and nothing looks faded; it only
     bites when content actually reaches the edge, which is exactly when it should. */
  mask-image:linear-gradient(to right,#000 calc(100% - 18px),transparent);
  -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 18px),transparent);
  padding-right:6px}
.re-diagram-bar > .re-chip-strip::-webkit-scrollbar,
.re-diagram-bar > .re-filter-bar::-webkit-scrollbar{display:none}
/* In a bar the cluster is a bar item, not an overlay — and the canvas's own copy steps aside
   so the two cannot both be on screen. */
.re-diagram-bar > .re-diagram-ctrls{position:static;margin-left:auto;flex:none}
.re-diagram-window:has(> .re-diagram-bar > .re-diagram-ctrls) > .re-diagram > .re-diagram-ctrls{display:none}
.re-diagram-note{padding:var(--re-space-3) var(--re-box-inset);border-top:1px solid var(--border);
  display:flex;flex-direction:column;gap:var(--re-space-1)}
/* the note's heading aligns with the note's own text — the box already supplies the inset */
.re-diagram-note > .re-section-label{--re-label-gutter:0px;margin:0}
.re-diagram-note > p{margin:0;font-size:calc(0.8125rem * var(--font-scale));line-height:1.6;
  color:var(--text-strong-muted)}
.re-diagram-legend{display:flex;flex-wrap:wrap;gap:var(--re-space-1) var(--re-space-4);
  padding:var(--re-space-2) var(--re-box-inset);border-top:1px solid var(--border);
  font-size:calc(0.6875rem * var(--font-scale));color:var(--muted-foreground)}
.re-diagram-legend > * {display:inline-flex;align-items:center;gap:var(--re-space-1)}
/* the dot reads the role tokens, so a key entry is `re-c-success` rather than an inline hex */
.re-diagram-legend-dot{width:8px;height:8px;border-radius:var(--radius-full);flex-shrink:0;
  background:var(--_solid,var(--muted-foreground))}

/* ── re-section-block — the heading and the thing it heads, as ONE unit ──────────
   A label belongs to its box the way an <h2> belongs to the paragraph under it, and that
   distance is the DS's to set — not the consumer's column gap. Without a wrapper the two
   are just siblings in someone's flex column, so `head, list, head, list` all sit at the
   same distance and the heading reads as floating between two boxes rather than sitting on
   one. The block owns the INTERNAL rhythm; separating one block from the next stays the
   parent's job, because that is what a gap column is for. */
.re-section-block{--re-section-gap:calc(10px * var(--density-mult,1));
  display:flex;flex-direction:column;gap:var(--re-section-gap)}
/* v14 §V14-3 — a hint that is a DIRECT child of the block is that section's description, so
   it takes the same gutter as the heading. The role comes from the container, not from a new
   class: `re-hint` is not always a section description (it also sits under a form field,
   where a gutter indent would be wrong), so putting padding on `.re-hint` itself would have
   been the wrong fix — and a consumer measured exactly that objection before we got here.

   The icon moves INTO the gutter rather than pushing the text along, same as the label. That
   is what the measurement actually found: without an icon the text sat 19px left of the
   heading, and WITH one it sat 3px right of it — 15px icon + 7px gap = 22px against a 19px
   gutter. So the icon'd case was never aligned either; it was close enough that nobody saw
   it. An alignment that comes out right because of a neighbouring component's width is not
   written down. */
.re-section-block > .re-hint{position:relative;padding-inline:var(--re-label-gutter);gap:0}
/* One `gap` was doing two jobs: separating the section's PARTS (label · box · explanation) and
   separating LINES inside the explanation. Equal, the grouping information is gone — a reader
   cannot tell from the spacing whether two sentences belong to the same thing. Reported with
   the measurement: box→hint 10px, hint→hint 10px, identical (labs §V29-1).
   Written as the DIFFERENCE between two named gaps rather than a bare negative number, because
   the difference IS the information: a line gap smaller than the part gap. Change either token
   and the relationship holds. */
.re-section-block > .re-hint + .re-hint{margin-top:calc(var(--re-hint-gap) - var(--re-section-gap))}
.re-section-block > .re-hint > svg{position:absolute;left:0;top:2px}
.re-section-block > .re-section-head,.re-section-block > .re-section-label{margin:0}
/* ── re-section-flush — the heading has no box to align to ───────────────────────
   re-section-label indents by --re-label-gutter because it heads THE BOX beneath it, so its
   text lands on that box's text inset. When what it heads is NOT a box — a chip strip, a row
   of people, a bare group of buttons — there is no text inset to meet, and the indent reads
   as exactly the error it exists to prevent: a heading floating right of everything under it.

   One token, so the label, its subline and the block's description all follow. Put it on the
   block, or on a standalone label.

   This is the small sibling of re-section-bleed: that one moves the BOX out to the heading,
   this one gives up the indent because there is no box. Both end with one text column. */
.re-section-flush{--re-label-gutter:0px}
.re-section-label svg{width:var(--re-label-icon);height:var(--re-label-icon)}

/* ── re-section-bleed — the section becomes ONE text column ──────────────────────
   The default is two columns and that is correct: a page title heads the PAGE so it sits at
   the container edge, while an re-section-label heads THE BOX under it so it sits at that
   box's text inset. Correct, and it still reads as an indent error when the two are stacked
   -- h1 at the edge, label 18px in, box edge further out again.

   This modifier collapses them. The heading keeps the container edge; the box is pulled
   outward by its own inset so the box's TEXT lands on that same edge. Nothing moves
   relative to the reader's eye except the box's frame, which is the one thing that was
   never in the text column to begin with. iOS and macOS Settings are built this way.

   It is a CHOICE, not a fix. Use it when a section is a page of stacked boxes and the
   heading column is what the reader tracks; keep the default when the boxes are cards in a
   grid, where a frame that overhangs its neighbours reads as a mistake.

   --_bleed is declared HERE, on the component, not on :root. That is deliberate and it is
   what keeps the knob working: a custom property is substituted at the element that
   declares it, so a :root-only --_bleed would resolve against :root's radius and inherit
   that number into a subtree with its own data-radius. Declared on the block, it resolves
   against whatever knob block the section is actually sitting in -- with no per-knob
   restatement to keep in sync.

   The pull is --re-label-gutter, not --re-box-inset: that token is already defined as the
   inset PLUS the box's 1px border, which is exactly the difference between where the box's
   frame starts and where its text does. Measured with --re-box-inset the three texts landed
   within 1px of each other at every setting -- the border, every time.

   The min() is the structural guarantee: the box can reach the page gutter's edge and can
   never go past it into horizontal scroll. Reaching it is by design -- on a phone a bled
   list runs to the screen margin, which is the platform convention. --re-gutter takes
   --re-label-gutter as its floor, so the cap does not bite at any density or radius; it is
   insurance, not the everyday path. */
.re-section-bleed{margin-inline:calc(-1 * var(--_re-bleed));--_re-bleed:min(var(--re-label-gutter),var(--re-gutter))}
/* the heading parts step back IN, and drop the box gutter -- they are the text column now,
   so they have nothing left to indent against */
.re-section-bleed > .re-section-head,
.re-section-bleed > .re-section-label,
.re-section-bleed > .re-hint{margin-inline:var(--_re-bleed);--re-label-gutter:0px}

/* ══ Navbar nav styles — alternatives to the default button-fill item ══
   Put a style modifier on re-navbar-nav; it recolors every item inside.
   default (button): filled hover/active · -text: color-only · -tabs: underline. */
.re-navbar-nav-text .re-navbar-item,
.re-navbar-nav-tabs .re-navbar-item{background:none}
.re-navbar-nav-text .re-navbar-item:hover,
.re-navbar-nav-tabs .re-navbar-item:hover{background:none;color:var(--foreground)}
.re-navbar-nav-text .re-navbar-item.active,.re-navbar-nav-text .re-navbar-item[aria-current="page"],
.re-navbar-nav-tabs .re-navbar-item.active,.re-navbar-nav-tabs .re-navbar-item[aria-current="page"]{background:none;color:var(--primary)}
/* active on hover → a softer shade of the active colour */
.re-navbar-nav-text .re-navbar-item.active:hover,
.re-navbar-nav-tabs .re-navbar-item.active:hover{color:color-mix(in srgb,var(--primary) 62%,var(--muted-foreground))}
/* tabs: active gets a 2px underline in the text colour, flush to the navbar's edge */
.re-navbar-nav-tabs{align-self:stretch}
.re-navbar-nav-tabs .re-navbar-item{height:100%;border-radius:0;border-bottom:2px solid transparent;margin-bottom:-2px}
.re-navbar-nav-tabs .re-navbar-item.active,.re-navbar-nav-tabs .re-navbar-item[aria-current="page"]{border-bottom-color:var(--primary)}

/* Navbar dropdown — item with a submenu; caret rotates, opens on hover. */
.re-navbar-dropdown{position:relative;display:inline-flex}
.re-navbar-caret{width:14px;height:14px;transition:transform .15s}
.re-navbar-dropdown:hover .re-navbar-caret,.re-navbar-dropdown:focus-within .re-navbar-caret{transform:rotate(180deg)}
.re-navbar-menu{position:absolute;top:calc(100% + 6px);left:0;min-width:184px;z-index:200;padding:6px;
  background:var(--card);border:1px solid var(--border-elevated);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);
  opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity .12s,transform .12s,visibility .12s}
.re-navbar-dropdown:hover .re-navbar-menu,.re-navbar-dropdown:focus-within .re-navbar-menu,
.re-navbar-menu.open{opacity:1;visibility:visible;transform:translateY(0)}  /* v2 A4 — .open state for touch (no hover; iOS gives buttons no focus → :focus-within dead). JS toggles .open, mirroring re-dropdown */
.re-navbar-menu-end{left:auto;right:0}                    /* right-aligned menu (e.g. a profile avatar) */
.re-navbar-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:var(--radius-inset);
  font-family:inherit;font-size:calc(0.875rem * var(--font-scale));color:var(--_text,var(--foreground));text-decoration:none;background:none;border:none;cursor:pointer;text-align:left;white-space:nowrap}
.re-navbar-menu-item svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
.re-navbar-menu-item:hover{background:color-mix(in srgb,currentColor 8%,transparent)}
.re-navbar-menu-item.active{color:var(--primary)}
.re-navbar-menu-sep{height:1px;background:var(--border);margin:6px 0}

/* ══════════════════════════════════════════════════════════════════
   AI / chat family (from the _labs §2.3 + itops §B1 gap reports).
   ══════════════════════════════════════════════════════════════════ */

/* ── re-prompt-bar — AI prompt / chat input ───────────────────────
   Left icon (mic/sparkle) · middle input or teaser text · round send.
   Teaser mode: re-prompt-text + re-prompt-caret. re-c-{role} recolors send. */
/* ── v6 V6-10 — elevation & border language ────────────────────────────────────────────
   One rule decides which border a surface reads:
     • it sits INSIDE a box (row divider, table rule, section edge) → --border
     • it OVERLAYS content and carries --shadow-md/lg → --border-elevated
   The second group is: prompt bar, tab-bar island, dropdown menu, navbar menu, popover,
   modal, toast, search modal. They now share one edge tone, so a screen speaks one
   language instead of each component having been written on its own day. */
/* v6 V6-10 — a floating input needs to separate from whatever scrolls under it, so it reads
   --border-elevated and --shadow-md. --shadow-sm is 8% alpha: enough on a desktop, invisible
   at phone scale where --card and --background are both near-white. */
/* The bar owns its type, not each slot. It used to be written twice — on .re-prompt-input
   AND on .re-prompt-text, which are alternatives to each other — and -sm overrode only the
   first, so an animated-text bar stayed 15px inside a small bar. Declaring it here also
   gives .re-prompt-icon a text to follow: it was reading 16px off the page root. */
.re-prompt-bar{font-size:calc(0.9375rem * var(--font-scale));
  display:flex;align-items:center;gap:8px;padding:5px 5px 5px 14px;
  background:var(--card);border:1px solid var(--border);   /* same reasoning as re-pull-badge: --shadow-md already separates it */border-radius:var(--radius-full);box-shadow:var(--shadow-md);transition:border-color .12s,box-shadow .12s}
.re-prompt-bar:focus-within{border-color:var(--_solid,var(--primary));box-shadow:0 0 0 3px color-mix(in srgb,var(--_solid,var(--primary)) 14%,transparent)}
.re-prompt-icon{flex-shrink:0;display:inline-flex;color:var(--muted-foreground)}
.re-prompt-icon svg{width:var(--re-icon);height:var(--re-icon)}
.re-prompt-input{flex:1;min-width:0;height:calc(var(--control-h-md) - 10px);border:none;background:none;outline:none;font-family:inherit;font-size:inherit;color:var(--foreground)}
.re-prompt-input::placeholder{color:var(--muted-foreground)}
.re-prompt-text{flex:1;min-width:0;color:var(--muted-foreground);white-space:nowrap;overflow:hidden}
.re-prompt-caret{display:inline-block;width:2px;height:1.05em;vertical-align:text-bottom;background:var(--primary);margin-left:1px;animation:re-blink 1.05s step-end infinite}
@keyframes re-blink{50%{opacity:0}}
.re-prompt-send,.re-prompt-voice{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:var(--control-h-md);height:var(--control-h-md);
  border-radius:50%;border:none;background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground));cursor:pointer;transition:opacity .12s}
.re-prompt-send:hover,.re-prompt-voice:hover{opacity:.9}
/* The three round buttons in the bar are --control-h-md across; their glyph is a fraction
   of that, so it follows density with the button instead of standing still at 18px. */
.re-prompt-send,.re-prompt-voice,.re-prompt-action{--re-prompt-btn-icon:calc(var(--control-h-md) * .43)}
.re-prompt-send svg,.re-prompt-voice svg{width:var(--re-prompt-btn-icon);height:var(--re-prompt-btn-icon)}
/* v5 V5-1 — secondary action (mic, attach, model picker): same round footprint as -send but
   borderless and unfilled, so send stays the single primary. Scales with the bar's size. */
.re-prompt-action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
  width:var(--control-h-md);height:var(--control-h-md);padding:0;border:none;border-radius:50%;
  background:none;color:var(--muted-foreground);font-family:inherit;cursor:pointer;transition:background .12s,color .12s}
.re-prompt-action svg{width:var(--re-prompt-btn-icon);height:var(--re-prompt-btn-icon)}
.re-prompt-action:hover:not(:disabled){background:color-mix(in oklch,var(--foreground) 6%,transparent);color:var(--foreground)}
.re-prompt-action:disabled{opacity:.4;cursor:not-allowed}
.re-prompt-bar-sm{font-size:calc(0.875rem * var(--font-scale));padding:4px 4px 4px 12px}
.re-prompt-bar-sm .re-prompt-send,.re-prompt-bar-sm .re-prompt-voice,.re-prompt-bar-sm .re-prompt-action{width:var(--control-h-sm);height:var(--control-h-sm)}
.re-prompt-bar-sm .re-prompt-input{height:calc(var(--control-h-sm) - 8px)}
.re-prompt-bar-lg{padding:7px 7px 7px 18px}
.re-prompt-bar-lg .re-prompt-send,.re-prompt-bar-lg .re-prompt-voice,.re-prompt-bar-lg .re-prompt-action{width:var(--control-h-lg);height:var(--control-h-lg)}
/* A LEADING round action (attach / model picker, ChatGPT's "+") replaces the text inset, so
   drop the extra left padding at every size. */
/* The + menu belongs to the BAR, not just to its button: the default offset is measured from
   the trigger, which lands the menu on the shell's bottom edge. Clear the bar's own padding
   and border so a real gap remains at every size. */
.re-prompt-bar .re-dropdown-menu{top:calc(100% + 12px)}
.re-prompt-bar-sm .re-dropdown-menu{top:calc(100% + 11px)}
.re-prompt-bar-lg .re-dropdown-menu{top:calc(100% + 14px)}
.re-prompt-bar:has(> :is(.re-prompt-action,.re-dropdown):first-child){padding-left:5px}
.re-prompt-bar-sm:has(> :is(.re-prompt-action,.re-dropdown):first-child){padding-left:4px}
.re-prompt-bar-lg:has(> :is(.re-prompt-action,.re-dropdown):first-child){padding-left:7px}
/* VOICE ⇄ SEND — the SOLID trailing slot shows one button: voice mode (re-prompt-voice,
   audio-lines) while the field is empty, send once there is text. A dictation mic, if any,
   is a separate borderless re-prompt-action and stays put through both states.
   CSS-only (:has() + :placeholder-shown), so the input must carry a placeholder. */
.re-prompt-bar:has(.re-prompt-voice):has(.re-prompt-input:placeholder-shown) .re-prompt-send{display:none}
.re-prompt-bar:has(.re-prompt-voice):has(.re-prompt-input:not(:placeholder-shown)) .re-prompt-voice{display:none}

/* ── re-bubble — chat bubble + typing indicator ───────────────────── */
.re-bubble{max-width:78%;width:fit-content;padding:8px 12px;border-radius:calc(var(--radius-surface) - 2px);background:var(--muted);color:var(--foreground);font-size:calc(0.875rem * var(--font-scale));line-height:1.45}
.re-bubble-mark{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);margin:0 0 3px 4px}

/* ══ Provenance — how a surface says "a machine made this" (labs §V32-1) ═══════════════
   One question asked in four places: a card heading, above a chat bubble, at the head of a
   list row, and inline while a tool is still running. Four local classes existed across the
   product family, all answering it differently, and the cost was already visible — a sibling
   product put `sparkles` on a RULE-BASED explanation because there was no mark for the other
   thing. So this is one surface with three parts, not four components.

   ⚠ It is NOT re-bubble-mark and NOT re-badge, and the distinction is the design:
     re-bubble-mark is a TEXT label answering "who said this" — a name.
     re-ai-mark is an ICON seal answering "what made this" — a provenance.
     re-badge is a STATE ("3 open"); re-ai-status is a PROCESS, and it has a live indicator
     in it, which a badge has no room for.
   The two marks can appear on the same bubble, and that is why the seal could not be folded
   into the label: one says Dana, the other says the model. */

/* ── The seal ── the same mark in all three places, so provenance reads the same everywhere.
   Sized from the type it sits with (em), not a pixel, so it follows density and any local
   font-size without a second number. */
.re-ai-mark{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
  width:1.7em;height:1.7em;border-radius:50%;
  background:color-mix(in oklab,var(--primary) 14%,transparent);color:var(--primary)}
.re-ai-mark > svg{width:60%;height:60%}
/* On a bubble it sits ABOVE the bubble, on the label's line — the bubble itself is the
   message and must not carry chrome inside it. */
.re-bubble-mark > .re-ai-mark{margin-right:5px;vertical-align:-0.4em}

/* ── The process line ── a tool is running and the interface is not stuck. Lives IN the
   flow, not in a corner: it is part of the transcript, and it is replaced by the result
   rather than dismissed. re-spinner is the DS's; only the row was missing. */
.re-ai-status{display:flex;align-items:center;gap:var(--re-space-2);
  padding:var(--re-space-2) var(--re-box-inset);
  border-radius:var(--radius-inset);background:var(--surface);
  font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-ai-status-name{font-weight:600;color:var(--foreground)}
/* Finished. The slot stays — the line must not jump when the tool resolves — but it is FILLED,
   not emptied: an invisible spinner holding its width reads as an unexplained indent, which is
   what a review saw. A check says the same thing the empty space was trying to say.
   Drawn from the spinner's own box rather than asking for different markup, so the consumer
   swaps one class on the row and nothing else. */
.re-ai-status-done{color:var(--muted-foreground)}
.re-ai-status-done > .re-spinner{animation:none;border-color:transparent;position:relative}
.re-ai-status-done > .re-spinner::after{content:"";position:absolute;
  left:50%;top:45%;width:32%;height:62%;
  border:2px solid var(--success);border-top:0;border-left:0;
  transform:translate(-50%,-50%) rotate(45deg)}

/* ── The derived row ── "this row's content was inferred, you did not write it".
   A TINT, not an inset shadow: the DS says provenance with the seal and grouping with a
   ground, and an inset shadow would be a third vocabulary for the same idea. The seal goes in
   re-row-lead, so the row does not need a second signal of its own — the tint groups, the
   seal names. */
.re-row-ai{background:color-mix(in oklab,var(--primary) 4%,transparent)}
.re-row-ai:hover{background:color-mix(in oklab,var(--primary) 7%,transparent)}
.re-bubble-time{display:block;font-size:calc(0.625rem * var(--font-scale));color:var(--muted-foreground);margin-top:3px}
.re-bubble-out{margin-left:auto;background:var(--primary);color:var(--primary-foreground)}
.re-bubble-out .re-bubble-time{color:color-mix(in srgb,var(--primary-foreground) 72%,transparent)}
/* Typing indicator — dots are direct children (re-dots is taken by the dot-grid effect). */
.re-typing{display:inline-flex;align-items:center;gap:4px;width:fit-content;padding:11px 14px;border-radius:calc(var(--radius-surface) - 2px);background:var(--muted)}
.re-typing span{width:6px;height:6px;border-radius:50%;background:var(--muted-foreground);animation:re-typing-bounce 1.2s infinite}
.re-typing span:nth-child(2){animation-delay:.15s}
.re-typing span:nth-child(3){animation-delay:.3s}
@keyframes re-typing-bounce{0%,60%,100%{opacity:.35;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}

/* ── re-timeline + re-slot — hour timeline ────────────────────────── */
.re-timeline{display:flex;flex-direction:column}
/* A run of slots that belongs together, with a rule between one run and the next. It exists
   because of what a consumer built for all-day events and it is the better answer to that
   problem than the band the DS shipped: re-cal-allday was a SECOND, differently shaped thing
   above the rail, and this is the same row shape with the time column saying something other
   than a time. One structure instead of two — the same move as re-row-eyebrow, where the fix
   was not a new container but letting the existing row say the extra thing.
   The rail needs nothing: it trims itself with .re-slot:first-child / :last-child, and those
   are relative to the GROUP, so each run gets its own line with a dot per row and the line
   stops at the run's ends. Two all-day events therefore extend the rail exactly like two timed
   ones do.
   The separator is between SIBLINGS, so the first group never carries a top rule and nobody
   has to remember which one is first. (kullanıcı, 2026-09-08 — `calendar`'ın tüm gün turu) */
/* Equal space on both sides of the rule has to mean equal OPTICAL space, and 20px each way
   did not give that: measured 32 above, 21 below. The 12px difference is re-slot-content's
   own `padding-bottom:12px` — the gap it keeps between consecutive rows — which on the LAST
   row of a group lands on top of the separator's margin instead of between two rows. The
   group's spacing is that gap there, so the row touching the rule gives it up.
   ⚠ min-height was my first guess and it was wrong: the row measured 54.8px, i.e. above the
   46px floor, so the floor was not adding anything. Measured before believing it. */
.re-timeline-group > .re-slot:last-child > .re-slot-content{padding-bottom:0}
.re-timeline-group + .re-timeline-group{margin-top:var(--re-space-4);padding-top:var(--re-space-4);border-top:1px solid var(--border)}
/* v5 V5-2 — an hour timeline is semantically a list, so it is often an <ol>/<ul>. Kill the
   UA markers and the 40px indent that would push the rail out of the content column. */
/* v5 §V5-2 — the UA's list defaults, reset on the timeline AND on the group, because a group
   is the natural <ul> once the rows are split into runs. Missing it, a consumer writes
   `style="margin:0;padding:0"` inline — which then beats the group's own separator spacing and
   silently flattens the gap under the rule to 1px. That is not hypothetical: this file's own
   demo did it, and the asymmetry was reported from the docs page. `margin-block` only for the
   group, so the separator's own margin-top survives. (labs v8 §V8-1 is the same lesson one
   component over: re-checklist and the 40px UA indent.) */
ol.re-timeline,ul.re-timeline{list-style:none;margin:0;padding:0}
ol.re-timeline-group,ul.re-timeline-group{list-style:none;margin-bottom:0;padding:0}
/* The time rail is derived from the SAME expression its text is, not from a constant. It was
   `52px` while `.re-slot-time` is `calc(0.75rem * var(--font-scale))`, so the column stood
   still while the label grew — and the label ran out of column at exactly the density
   `calendar` ships as its default. Measured, intrinsic label width against the 52px column:
       compact  47.9 / 52  (+4.1)     standard  50.4 / 52  (+1.6)
       comfortable  53.4 / 52  (-1.4)  <- overflows
   ⚠ `ch` cannot be used here, and the reason is the same trap this file keeps hitting: a
   length on `.re-slot` resolves against `.re-slot`'s OWN font, which is the inherited body
   face at body size — not the 0.75rem mono the rail is set in. So the width is written in
   `rem`, which depends on nothing local, times the same --font-scale the type reads.
   8 characters, and 0.6em per character is MEASURED, not assumed: with the DS's --font-mono
   (JetBrains Mono) 1ch came to exactly 0.600em at all three densities, `09:30` measured
   exactly 5ch and `Tüm gün` exactly 7ch. 8 gives the longest label the DS promises a full
   character of room instead of the 1.6px it had. --re-slot-time-w is the knob: a consumer
   whose labels are longer (`Ganztägig` is 9) raises it, and one who overrides --font-mono
   with a wider face does too. (labs §V25-1) */
.re-slot{--re-slot-time-w:calc(8 * 0.6 * (0.75rem * var(--font-scale)));display:grid;grid-template-columns:var(--re-slot-time-w) 20px 1fr;gap:10px;min-height:46px}
/* The time label and its dot have to sit on ONE line, and that only works if the geometry is
   knowable: with line-height:normal the text box height came from the font, so the two nudges
   had to agree by luck and the label ended up 2px below the dot (measured: text centre 30,
   dot centre 28). An explicit line-height makes both derivable — text centre is
   padding-top + 16/2 = 10px, dot centre is margin-top + 10/2 = 10px. */
.re-slot-time{font-size:calc(0.75rem * var(--font-scale));font-family:var(--font-mono);color:var(--muted-foreground);text-align:right;
  line-height:16px;padding-top:2px}
.re-slot-rail{position:relative;display:flex;justify-content:center}
.re-slot-rail::before{content:"";position:absolute;top:0;bottom:0;left:50%;transform:translateX(-50%);width:2px;background:var(--border)}
.re-slot:first-child .re-slot-rail::before{top:7px}
.re-slot:last-child .re-slot-rail::before{bottom:auto;height:7px}
.re-slot-dot{position:relative;z-index:1;width:10px;height:10px;margin-top:5px;border-radius:50%;background:var(--card);border:2px solid var(--border)}   /* 5 + 10/2 = 10px, the same centre as the time label above */
.re-slot-content{padding-bottom:12px;min-width:0}
.re-slot.now .re-slot-dot{background:var(--primary);border-color:var(--primary)}
.re-slot.now .re-slot-time{color:var(--primary);font-weight:600}
.re-slot.past{opacity:.5}

/* ── re-day — week-strip day box ──────────────────────────────────── */
.re-day{display:inline-flex;flex-direction:column;align-items:center;gap:3px;width:46px;padding:7px 4px;
  background:none;border:1px solid transparent;border-radius:var(--radius-md);cursor:pointer;font-family:inherit;transition:background .12s,border-color .12s}
.re-day:hover{background:color-mix(in srgb,var(--foreground) 5%,transparent)}
.re-day-name{font-size:calc(0.625rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--muted-foreground)}
.re-day-num{font-size:calc(0.9375rem * var(--font-scale));font-weight:600;color:var(--foreground)}
.re-day-dots{display:flex;gap:2px;height:4px;margin-top:1px}
.re-day-dots i{width:4px;height:4px;border-radius:50%;background:color-mix(in srgb,var(--primary) 45%,transparent)}
.re-day.today .re-day-num{color:var(--primary)}
.re-day.warn .re-day-num{color:var(--warning)}
.re-day.active{background:var(--primary);border-color:var(--primary)}
.re-day.active .re-day-name,.re-day.active .re-day-num{color:var(--primary-foreground)}
.re-day.active .re-day-dots i{background:color-mix(in srgb,var(--primary-foreground) 60%,transparent)}

/* ── re-source — small per-channel source badge ───────────────────── */
/* v7 — all three carry line-height:1 (re-chip already did). Without it the height comes from
   whatever line-height they INHERIT, so the same badge measured 26px inside a card and 29.6px
   inside a re-field-value (line-height 1.6) — one component, two heights, depending where you
   dropped it.
   v7 — metrics deliberately IDENTICAL to re-badge and re-tag: same padding, font size and
   border box, so the three line up when they share a row on a card (they were 17px against
   25px, which read as a mistake). What separates them is shape and weight, not size:
   re-source is fully round and 600, re-tag is a rounded square and 500 — a source outranks a
   label. The default tint stays neutral; re-c-{role} tints it per provider when a consumer
   wants that. --_src-gap exists so the identity separator can cancel it out below. */
.re-source{--_src-gap:5px;
  display:inline-flex;align-items:center;gap:var(--_src-gap);
  padding:var(--re-space-1) var(--re-space-2);border-radius:var(--radius-full);
  border:1px solid var(--_edge,transparent);
  background:var(--_tint,color-mix(in srgb,var(--muted-foreground) 14%,transparent));color:var(--_text,var(--foreground));
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;line-height:1;white-space:nowrap}

/* ── re-attn — attention row (left-accent list item) ──────────────── */
.re-attn{display:flex;align-items:center;gap:10px;padding:10px 14px;border-left:3px solid var(--_solid,var(--warning));
  background:var(--card);font-size:calc(0.875rem * var(--font-scale));line-height:1.4;color:var(--foreground)}
.re-attn svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0;color:var(--_solid,var(--warning))}
.re-attn-body{flex:1;min-width:0}

/* ══════════════════════════════════════════════════════════════════
   Domain components (from the _labs §2.3 gap report) — money, charts,
   selection tiles, wizard, weather, entry gate, thumbnail.
   ══════════════════════════════════════════════════════════════════ */

/* ── re-money / re-amount — tabular money ─────────────────────────── */
.re-money{font-variant-numeric:tabular-nums;font-weight:600;color:var(--foreground);white-space:nowrap}
.re-money-pos{color:var(--success)}
.re-money-neg{color:var(--danger)}
.re-money-lg{font-size:calc(1.375rem * var(--font-scale))}
.re-amount{display:inline-flex;align-items:baseline;gap:5px;font-variant-numeric:tabular-nums;white-space:nowrap}
.re-amount-currency{font-size:calc(1rem * var(--font-scale));font-weight:500;color:var(--muted-foreground)}
.re-amount-value{font-size:calc(2.25rem * var(--font-scale));font-weight:700;line-height:1;color:var(--foreground)}
/* v2 A13 — re-money-input: the *input* counterpart of re-amount (big-figure entry with a
   focus ring and a trailing currency). re-amount is display-only; this holds an <input>. */
.re-money-input{display:flex;align-items:baseline;gap:6px;padding:12px 16px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-surface);transition:border-color .12s,box-shadow .12s}
.re-money-input:focus-within{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 18%,transparent)}
.re-money-input input{flex:1;min-width:0;width:100%;border:none;background:none;outline:none;font-family:inherit;font-size:calc(1.5rem * var(--font-scale));font-weight:700;color:var(--foreground);font-variant-numeric:tabular-nums}
.re-money-input input::placeholder{color:var(--muted-foreground);font-weight:600}
.re-money-input-suffix{flex-shrink:0;font-size:calc(1rem * var(--font-scale));font-weight:500;color:var(--muted-foreground)}

/* v5 V5-3 — re-card-stack: the content-card counterpart of re-sheet-body's rhythm. re-card is
   only a surface (bg + border + radius + shadow); re-card-body's padding is a ROW padding
   (asymmetric --pad-y/--pad-x, no gap). A section box wants symmetric padding AND a gap
   between its children. Use together: <div class="re-card re-card-stack">. */
.re-card-stack{display:flex;flex-direction:column;gap:var(--re-space-3);padding:var(--re-space-4)}

/* ── re-bar-signed — balance bar filling both ways from centre ────── */
.re-bar-signed{position:relative;height:8px;background:var(--muted);border-radius:999px;overflow:hidden}
.re-bar-signed-mid{position:absolute;left:50%;top:0;bottom:0;width:1px;background:var(--border);z-index:1}
.re-bar-signed-fill{position:absolute;top:0;bottom:0;background:var(--muted-foreground)}
.re-bar-signed-fill.pos{left:50%;background:var(--success)}
.re-bar-signed-fill.neg{right:50%;background:var(--danger)}

/* ── re-transfer — "A → B · 1.200,00 ₺ · [Paid]" settle-up row ─────── */
.re-transfer{display:flex;align-items:center;gap:10px;padding:11px 14px;background:var(--card);border:1px solid var(--border-surface);border-radius:var(--radius-md)}
.re-transfer-names{display:inline-flex;align-items:center;gap:6px;font-size:calc(0.875rem * var(--font-scale));font-weight:600;color:var(--foreground);min-width:0}
.re-transfer-arrow{color:var(--muted-foreground);flex-shrink:0}
.re-transfer-arrow svg{width:var(--re-icon);height:var(--re-icon);display:block}
.re-transfer .re-money{margin-left:auto}
.re-transfer.mine{border-color:color-mix(in srgb,var(--primary) 32%,transparent);background:color-mix(in srgb,var(--primary) 5%,transparent)}

/* ── re-ring — progress ring + a centred label ────────────────────── */
.re-ring{position:relative;display:inline-flex}
.re-ring-label{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1;font-size:calc(0.6875rem * var(--font-scale));font-weight:700;color:var(--foreground);font-variant-numeric:tabular-nums}  /* v2 A15 — defined base so a small ring (≈46px) doesn't inherit 14px and overflow; span scales via .6em; tabular so 8%→100% doesn't jump. Bigger rings override font-size. */
.re-ring-label span{font-size:.6em;font-weight:500;color:var(--muted-foreground)}

/* ── re-tile — big touch selection card ───────────────────────────── */
.re-tile{position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:20px 16px;
  background:var(--card);border:1px solid var(--border);border-radius:var(--radius-surface);cursor:pointer;font-family:inherit;transition:border-color .12s,background .12s}
.re-tile:hover{border-color:color-mix(in oklch,var(--brand) 30%,transparent)}
.re-tile-icon{font-size:1rem;--re-icon-ratio:1.4;   /* the subject glyph of a selection card, bigger than its title. font-size is written HERE on purpose: the slot has no text of its own, so `em` would otherwise resolve against whatever the page root happens to be. */
  display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:var(--radius-md);background:color-mix(in srgb,var(--primary) 12%,transparent);color:var(--primary)}
.re-tile-icon svg{width:var(--re-icon);height:var(--re-icon)}
.re-tile-title{font-size:calc(0.9375rem * var(--font-scale));font-weight:600;color:var(--foreground)}
.re-tile-sub{font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-tile-check{--re-tile-check-h:20px;--re-tile-check-icon:calc(var(--re-tile-check-h) * .6);
  position:absolute;top:10px;right:10px;display:none;align-items:center;justify-content:center;width:var(--re-tile-check-h);height:var(--re-tile-check-h);border-radius:50%;background:var(--primary);color:var(--primary-foreground)}
.re-tile-check svg{width:var(--re-tile-check-icon);height:var(--re-tile-check-icon)}
.re-tile.on{border-color:var(--primary);background:color-mix(in srgb,var(--primary) 6%,transparent)}
.re-tile.on .re-tile-check{display:inline-flex}
.re-tile-soon{opacity:.55;cursor:default}
.re-tile-soon:hover{border-color:var(--border)}

/* ── re-wizard — full-screen multi-step flow shell ────────────────── */
.re-wizard{display:flex;flex-direction:column;height:100dvh;background:var(--background)}
/* v2 A16 — a full-screen flow starts at the very top of the screen (not under a navbar),
   so the head must clear the notch/dynamic-island itself. re-wizard-foot already clears the
   bottom inset; this mirrors it on top. min-height is preserved. */
.re-wizard-head{flex-shrink:0;display:flex;align-items:center;gap:12px;min-height:56px;padding:0 16px;padding-top:env(safe-area-inset-top);border-bottom:1px solid var(--border)}
.re-wizard-body{flex:1;overflow-y:auto;padding:20px 16px}
/* v2 A17 — a step heading level between re-ph-title (page) and re-section-title (too big here). */
.re-wizard-title{font-size:calc(1.25rem * var(--font-scale));font-weight:700;color:var(--foreground);margin:0}
.re-wizard-lead{font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground);margin:4px 0 0}
.re-wizard-w{width:100%;max-width:480px;margin:0 auto}
.re-wizard-body > .re-wizard-w{display:flex;flex-direction:column;gap:20px}  /* v2 A17 — step section rhythm (like re-sheet-body / re-empty-state) */
.re-wizard-foot{flex-shrink:0;display:flex;align-items:center;gap:8px;min-height:64px;padding:12px 16px calc(12px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}
.re-wizard-foot > .re-wizard-w{display:flex;gap:8px}  /* v2 A17 — width-capped footer keeps its button row */
.re-wizard-foot .re-btn{flex:1}                       /* two-equal / full-width single */

/* ── re-weather — horizontal daily strip ──────────────────────────── */
.re-weather{display:flex;gap:8px}
.re-weather-day{display:flex;flex-direction:column;align-items:center;gap:5px;min-width:66px;padding:10px 8px;background:var(--card);border:1px solid var(--border-surface);border-radius:var(--radius-md)}
.re-weather-date{font-size:calc(0.625rem * var(--font-scale));font-weight:600;text-transform:uppercase;letter-spacing:.03em;color:var(--muted-foreground)}
.re-weather-icon{font-size:1rem;--re-icon-ratio:1.4;display:inline-flex;color:var(--warning)}   /* same case as re-tile-icon: a subject glyph in a textless slot */
.re-weather-icon svg{width:var(--re-icon);height:var(--re-icon)}
.re-weather-temp{font-size:calc(0.8125rem * var(--font-scale));font-weight:600;color:var(--foreground);font-variant-numeric:tabular-nums}
.re-weather-temp span{color:var(--muted-foreground);font-weight:400}
.re-weather-rain{display:inline-flex;align-items:center;gap:2px;font-size:calc(0.625rem * var(--font-scale));color:var(--info);font-variant-numeric:tabular-nums}
.re-weather-rain-off{color:var(--muted-foreground);opacity:.7}  /* v2 A18 — low chance: present but recedes (vs --info emphasis) */
.re-weather-badge{font-size:calc(0.625rem * var(--font-scale));font-weight:600;color:var(--primary)}
.re-weather-day.today{border-color:color-mix(in srgb,var(--primary) 55%,var(--border));background:color-mix(in srgb,var(--primary) 6%,transparent)}  /* v2 A18 — which day is "today" */

/* ── re-gate — entry / invite gate (centred column, full height) ──── */
/* v2 A7 — the gate opens WITHOUT the re-app shell (which owns scrolling), so it must
   honour the safe-area itself. v2 A8 — gap lets a brand/byline block sit above the card.

   ⚠ align-content:center is what MAKES that second sentence true, and for two years it was
   missing. `place-items` sets align-/justify-ITEMS — where an item sits inside its track — and
   says nothing about how the TRACKS are packed. With align-content at its initial `normal`, a
   grid stretches its auto rows, so the gap was never what separated the two children: each row
   absorbed half the free height and centred its own item inside it. Measured at 900px with a
   byline: card 172.8px ABOVE the screen's centre, byline 321.5px below the card instead of 15.
   One child measured 0.0 off-centre, which is why this survived — the promise only broke in
   the arrangement the canonical markup actually shows, and a screen with no byline proved the
   rule every time somebody checked. With align-content:center: gap exactly 15.0.

   The residual is a product fact, not a bug, and gate.mdx states it: with a byline present the
   thing centred is the STACK, so the card sits half the byline above centre (19.5px measured).
   A screen that wants the form dead centre does not carry a byline. */
/* The ground is part of this component: a sign-in screen is a page, and a page with no
   background of its own inherits whatever the host happens to have — every consumer picked a
   different one. --re-gate-bg is its own token because the two themes want opposite
   relationships to --surface: light wants to be lighter than it (98% read as grey behind a
   white card), dark wants exactly it. Defined per theme in base.css. */
/* ⚠ The gap reads --re-space-3, it is not a raw 16px, and that was a real defect twice over.
   A raw number does not follow data-density — at compact every other rhythm on the screen
   shrank and the gate's did not — and it is not expressible by any utility, because the scale
   is 5px-based (re-gap-3 = 15, re-gap-4 = 20; 16 is between them). A consumer who wraps the
   field and the button in a box to group them therefore CANNOT restore the card's own rhythm:
   two products independently reached for re-gap-2 and landed at 10px. The 1px this costs at
   standard is invisible; being off the scale was not. See the note under Composition below —
   the wrapper is still the wrong move, but now recovering from it is possible. */
.re-gate{background:var(--re-gate-bg);
  min-height:100dvh;display:grid;place-items:center;align-content:center;gap:var(--re-space-3);padding:calc(24px + env(safe-area-inset-top)) 24px calc(24px + env(safe-area-inset-bottom))}
.re-gate-card{width:100%;max-width:380px;display:flex;flex-direction:column;gap:var(--re-space-3)}
/* The providers are one row of equal columns, because they are alternatives to each other —
   three stacked full-width buttons read as three steps. auto-fit, so they stack on a screen
   too narrow to hold the longest name. The label is the brand and nothing else: "Continue
   with" repeated three times is the same three words read three times. */
.re-gate-providers{display:grid;grid-template-columns:repeat(auto-fit,minmax(104px,1fr));gap:var(--re-space-2)}
.re-gate-providers > .re-btn{padding-inline:var(--re-space-2);gap:var(--re-space-1);min-width:0}
/* ── re-gate-mark — the app's own mark, at the head of an identity-less flow ────────
   Five consumers wrote this rule under their own prefix (pl- sp- id- tk- ct-) and all five are
   identical to the character, border included — so the values below are theirs, verbatim, and
   migrating is a rename with nothing to look at. It is NOT scoped to re-gate-card: the same
   mark heads an invite screen and an identity picker, both outside the card.

   Not to be confused with re-brandmark, below: that is a PROVIDER's logo inside a sign-in
   button, icon-sized and owned by Google/Apple/Microsoft. This is the product's own mark.

   The corner is 16px at the default radius step, as the copies had it, but it follows the knob:
   4/3 of --radius-md (12 → 16), capped so a 54px tile never rounds into a circle at the upper
   steps — half its side is 27, and the cap stays well under it.

   The glyph is sized here, not at the call site. The copies relied on `icon(x, 26)` and one
   screen passed 24, so a single project drew two sizes of its own mark. 0.5 of the tile is the
   ratio re-avatar uses, and 0.5 of the 52px content box is 26 — the number most call sites had
   already chosen. A CSS width wins over the svg's width attribute, so the stray 24 becomes 26
   on its own. */
.re-gate-mark{width:54px;height:54px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
  border-radius:min(calc(var(--radius-md) * 4 / 3),1.25rem);
  background:color-mix(in srgb,var(--brand) 14%,var(--card));color:var(--brand);
  border:1px solid color-mix(in oklch,var(--brand) 28%,transparent)}
.re-gate-mark > svg{width:calc(var(--re-gate-mark-glyph,.5) * 100%);height:calc(var(--re-gate-mark-glyph,.5) * 100%);flex-shrink:0}
/* A brand mark is not a DS icon — it is multi-colour, it belongs to whoever owns it, and the
   registry skips it by name (icon-inventory). It still takes the icon size, so it sits in a
   button the same way ours do. */
.re-brandmark{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}

/* ── re-thumb — small image preview (receipt / attachment) ────────── */
.re-thumb{--re-thumb-h:44px;--re-thumb-icon:calc(var(--re-thumb-h) * .41);
  display:inline-flex;align-items:center;justify-content:center;width:var(--re-thumb-h);height:var(--re-thumb-h);flex-shrink:0;
  border-radius:var(--radius-md);background:var(--muted);border:1px solid var(--border);color:var(--muted-foreground);overflow:hidden}
.re-thumb img{width:100%;height:100%;object-fit:cover}
.re-thumb svg{width:var(--re-thumb-icon);height:var(--re-thumb-icon)}
.re-thumb-lg{--re-thumb-h:64px}   /* one knob: the box AND its placeholder glyph follow (the glyph used to stay 18px in a 64px box) */


/* ══ re-pull-refresh — pull down to refresh DATA (v6 V6-11) ══════════════════════════
   The browser's own pull gesture reloads the PAGE, which throws away state and scroll
   position; an app wants to refresh its data and keep both. So the host kills the native
   overscroll (`overscroll-behavior:none` on html/body — still the host's job: re-main carries
     overscroll-behavior-y:contain for its OWN chaining, but the document's bounce is outside
     the DS's reach)
   and drives this indicator instead.
   Parameters below are measured, not guessed (heyre, four labs projects wanted the same):
     scroll container .re-main · resistance 1:2 · threshold 64px · ceiling 96px
     transition 180ms ease-out, OFF while dragging (or the badge lags the finger)
   Four traps, all of them hit in practice:
     1 NO CLIPPING. An early version grew a wrapper with overflow:hidden — the badge showed
       its clip box, and while parked offscreen its --shadow-lg (40px blur) bled into view as
       an unexplained glow at the top of the page. Park it with translateY + opacity:0.
     2 The gesture must not START inside a horizontally scrollable strip (re-chip-strip,
       re-page-tabs, re-scroll-x): a diagonal swipe there both triggers a refresh and locks
       horizontal panning via preventDefault. Test BEHAVIOUR, not class names — overflow-x
       auto/scroll AND scrollWidth > clientWidth — and abort once movement is mostly sideways.
     3 Only from the top: scrollTop must be 0, or a scroll that reaches the top mid-flick
       turns into a refresh nobody asked for.
     4 Disabled while a sheet or modal is open — that panel scrolls on its own.
   Composes with Composes with .re-app{touch-action:manipulation}: that only removes double-tap zoom;
   panning and pinch still work. The gesture itself is a documented snippet (docs → Pull to
   refresh), the same way the sidebar and search behaviours are — @redevme/ui ships no JS. */
.re-pull{position:absolute;top:0;left:0;right:0;z-index:30;display:flex;justify-content:center;   /* 30, NOT 20: re-page-header is also 20 and comes LATER in the DOM, so at an equal z-index it
   paints on top — and its background is opaque, so it swallowed the indicator whole. The
   geometry was right the whole time (top:0 of re-pull-host); only the paint order was wrong,
   which is why it read as "the indicator doesn't start at the top". 30 is the app-shell
   overlay tier it shares with re-tabbar-float — they sit at opposite edges and never meet. */
  
  pointer-events:none;   /* the indicator is never a target — the finger belongs to the scroll area */
  transform:translateY(var(--_pull-y,0px));opacity:var(--_pull-o,0);
  transition:transform .18s ease-out,opacity .18s ease-out}
.re-pull-dragging{transition:none}   /* while the finger is down the badge must track it exactly */
.re-pull-badge{display:inline-flex;align-items:center;gap:var(--re-space-2);
  margin-top:var(--re-space-3);padding:calc(var(--re-space-1) + 2px) var(--re-space-4);
  background:var(--card);border:1px solid var(--border);border-radius:var(--radius-full);
  box-shadow:var(--shadow-lg);   /* --border, not --border-elevated: the shadow is what lifts this off the content, and the two together read as a heavy outline (light --border-elevated is 82% against --border 90%) */
  font-size:calc(0.75rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);
  white-space:nowrap}
/* The rotation is a KNOB, not a hard-coded behaviour. Two consumers wanted two different
   idioms — flip once at the threshold, versus a spinner that tracks the pull degree by degree —
   and a CSS rule that fixes one turns the other into an override fight. So the DS owns the
   slot, the transition and the reduced-motion handling, and --re-pull-rotate owns the angle:
   leave it alone for the default flip, or drive it from the gesture (`el.style.setProperty
   ('--re-pull-rotate', deg + 'deg')`) for a follow-the-finger spinner. */
.re-pull-icon{display:inline-flex;color:var(--brand);
  transform:rotate(var(--re-pull-rotate,0deg));transition:transform .18s ease-out}
.re-pull-icon svg{width:var(--re-icon);height:var(--re-icon)}
/* NOT re-spinner: it carries its own `animation`, which collides with the rotation driven
   here, and switching that off is the partial override the knobs above exist to avoid.
   Put a plain glyph in the slot (lucide loader-circle for the busy state). */
.re-pull-dragging .re-pull-icon{transition:none}   /* a per-pixel angle must not lag the finger */
.re-pull-ready .re-pull-badge{color:var(--brand)}
.re-pull-ready{--re-pull-rotate:180deg}   /* default idiom: "let go now" — the arrow flips at the threshold */
/* Busy spin: duration and PHASE are both knobs. A consumer whose icon tracked the pull is
   already at some angle when the finger lifts, and a fresh animation snaps back to 0deg — the
   spin has to pick up where the drag left off. A negative delay does exactly that, so
   --re-pull-spin-delay takes `-(angle mod 360)/360 * duration`. Without it the only way out is
   overriding the animation itself, which is the partial-override fight the rotate knob exists
   to avoid. */
.re-pull-busy .re-pull-icon{animation:re-pull-spin var(--re-pull-spin-dur,.8s) linear infinite;
  animation-delay:var(--re-pull-spin-delay,0s)}
/* The `from` is EXPLICIT on purpose. With only a `to`, the implicit start is the element's
   current computed transform — i.e. --re-pull-rotate — so a consumer still writing that knob
   while busy (updating the status text, say) moved the animation's origin and the icon jumped
   back and forth visibly. Spelling out `from` makes the knob inert during the spin, so the
   phase is controlled solely by --re-pull-spin-delay. Enforced here rather than written up as
   a "don't touch it while busy" rule nobody would read. */
@keyframes re-pull-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .re-pull{transition:none}
  .re-pull-busy .re-pull-icon{animation:none}
}
/* The scroll container needs a positioned ancestor for the indicator to hang from. re-main
   is not positioned by default, so the host says so once. */
.re-pull-host{position:relative}

/* ══ TASK FIELDS — board / roadmap / detail panel share these (v7 Faz 1) ══════════════
   The field set behind every view: title · description · assignee · reporter · start ·
   due · status. Deliberately small: six additions, and three of the needs turned out to
   be patterns over primitives that already exist rather than new components. */

/* ── re-field — READ-ONLY field layout (label + value) ───────────────────────────────
   re-input-label is for forms; this is for showing a value. Stacks below 480px, where a
   104px label column would leave nothing for the value. An empty value renders an em
   dash rather than collapsing the row, so a missing field still reads as a field. */
.re-field-list{display:flex;flex-direction:column;gap:var(--re-space-3)}
.re-field{display:grid;grid-template-columns:104px minmax(0,1fr);gap:var(--re-space-3);align-items:baseline}
.re-field-label{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;
  letter-spacing:.03em;color:var(--muted-foreground)}
.re-field-value{min-width:0;font-size:calc(0.8125rem * var(--font-scale));line-height:1.6;color:var(--foreground)}
.re-field-value:empty::before{content:"—";color:var(--muted-foreground)}
.re-field-value > .re-field-multi{margin:0;white-space:pre-line}   /* description: keep the author's line breaks */
@media (max-width:480px){.re-field{grid-template-columns:minmax(0,1fr);gap:3px}}

/* ── re-source: the account half, and sync health ────────────────────────────────────
   A source is a CONNECTION — (provider, account) — not a provider: the same person
   connects a personal and a work Google account, and both profiles can read "Ersin
   Yılmaz", so the account half is a name THEY chose and runs up to 40 characters. It
   gets its own truncating slot, capped in ch so the cap follows the font, and the pill
   never grows past a line.
   Sync health is a SEPARATE signal: being connected doesn't mean the last sync worked.
   A dot carries it, so "connected" (the pill exists) and "sync failing" (the dot) can't
   be mistaken for each other — and it costs no extra markup. */
/* The separator has to sit EQUIDISTANT from both names, and the pill's flex gap lands only
   on the left of this element — so it was 9px before the dot and 4px after. Cancel the gap
   here and let the dot's own margin do the spacing on both sides. */
.re-source-account{margin-left:calc(-1 * var(--_src-gap));
  max-width:18ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-weight:500;color:var(--muted-foreground)}
.re-source-account::before{content:"·";margin-inline:4px;opacity:.55}
.re-source-warn::after,.re-source-error::after{content:"";width:6px;height:6px;flex-shrink:0;
  border-radius:50%;margin-left:1px}
.re-source-warn::after{background:var(--warning)}
.re-source-error::after{background:var(--danger)}

/* ── re-daterange — start + due together ────────────────────────────────────────────
   auto-fit rather than two fixed columns: side by side while there's room, stacked when
   there isn't, with no breakpoint to keep in step. The inputs are plain
   .re-input[type=date], which already carries the iOS fix from v6 §V6-7 —
   appearance:none and min-width:0 — so a range doesn't reintroduce that overflow. */
.re-daterange{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:var(--re-space-2);
  max-width:420px}   /* a date field stretched to 337px in a desktop column and looked broken; two fields plus the gap fit inside this and a consumer can override it */

/* ── status: one appearance everywhere, editable or not ──────────────────────────────
   Status is a re-badge, full stop — in a card, in a field list, in a detail panel. A native
   <select> was tried and dropped: it made the SAME value look like two different things
   depending on whether you could edit it, and "what is the status" is asked far more often
   than "change the status".
   Editable = the badge becomes a button that opens a re-dropdown of badges. Same shape as
   the person picker: the trigger IS the display component. Put a chevron inside for the
   affordance; both fill styles are already there — tinted by default, re-badge-solid for
   the full-colour version.
   NOTE: making a badge interactive drags in the touch-target layer — see the
   (pointer: coarse) block, where button/a.re-badge is listed. */
/* Ratio 1, declared: at 11px type the glyph is ALREADY taller than the letters (an 11px
   font has ~8px of cap height), so 1.15 grew the pill. Measured — the plan's 0.8 was
   derived against the wrong font-size and would have drawn 8.8px, not the 10px it named.
   1 keeps today's 11px exactly, at every density. */
.re-badge,.re-tag,.re-source{--re-icon-ratio:1}
.re-badge svg,.re-tag svg,.re-source svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
button.re-badge,a.re-badge{cursor:pointer;font-family:inherit;transition:background .12s,border-color .12s}
button.re-badge:hover,a.re-badge:hover{background:color-mix(in oklch,var(--_solid,var(--foreground)) 20%,transparent)}
button.re-badge-solid:hover,a.re-badge-solid:hover{background:var(--_solid,var(--primary));filter:brightness(1.06)}
button.re-badge:disabled{opacity:.55;cursor:not-allowed}
.re-badge-caret{opacity:.6;transition:transform .12s}
button.re-badge[aria-expanded="true"] .re-badge-caret{transform:rotate(180deg)}

/* ── person picker: no new component ────────────────────────────────────────────────
   re-person is already an avatar + a name, and re-person-ghost is already the dashed
   unassigned state. So a picker is a re-person trigger over a re-dropdown of re-person
   items; all that was missing is that a <button>/<a> version didn't look pressable. */
button.re-person,a.re-person{cursor:pointer;font-family:inherit;transition:border-color .12s,background .12s}
button.re-person:hover,a.re-person:hover{border-color:color-mix(in oklch,var(--brand) 32%,transparent)}
button.re-person:disabled{opacity:.55;cursor:not-allowed}

/* ── re-tag-input — chips and a text field on one surface (v7 §V7-6) ─────────────────
   Typing a word and pressing Enter turns it into a chip. The box has to LOOK like one
   input even though it holds several elements, so the border and the focus ring belong to
   the wrapper — the inner field is borderless and transparent — and cursor:text means
   clicking the padding lands you in the field rather than nowhere.
   The field grows to fill the last row and wraps to a new one when it can't
   (flex:1 1 <basis>), so a long list of chips doesn't squeeze the caret to nothing.
   NOTE: it is a form control, so it is in the (pointer: coarse) 16px list — otherwise iOS
   zooms the page on focus. That is the five-layer check, not an afterthought. */
.re-tag-input{display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  min-height:var(--control-h-md);padding:5px 8px;cursor:text;
  border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card);
  transition:border-color .12s,box-shadow .12s}
.re-tag-input:focus-within{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 18%,transparent)}
.re-tag-input-field{flex:1 1 84px;min-width:84px;padding:0;height:calc(var(--control-h-md) - 14px);
  border:none;background:none;outline:none;font-family:inherit;color:var(--foreground);
  font-size:calc(0.8125rem * var(--font-scale))}
.re-tag-input-field::placeholder{color:var(--muted-foreground)}
/* A tag has ONE shape in the family: the small pill the note card shows (re-badge-sm, Ersin 2026-10-06). Inside the
   input the chip keeps re-tag's size (its × needs the room) and takes the pill corner, so writing a tag and reading it
   look like the same object. The FRAME stays a form control's corner (radius-md, as re-textarea): tasks' tag box was a
   re-chip-bar, a pill frame among square fields ("tüm form rounded square bu full rounded kalıyor"). */
.re-tag-input > .re-tag{border-radius:var(--radius-full)}
/* Removal lives ON the tag, so a tag is removable anywhere — a filter bar, a note, a card. */
.re-tag-remove{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
  width:1em;height:1em;margin-right:-3px;padding:0;border:none;border-radius:50%;   /* 1em, NOT 14px: a 14px button was taller than the tag's 11px line box and grew the pill to 26px where a plain tag is 23px — the same "one component, two heights" bug, reintroduced by its own child */
  background:none;color:inherit;opacity:.55;cursor:pointer;
  font-family:inherit;font-size:inherit;   /* a <button> does NOT inherit font-size — the UA gives it 13.333px, so `1em` resolved to that and the pill grew to 25.3px instead of 23px */
  transition:opacity .12s,background .12s}
.re-tag-remove:hover{opacity:1;background:color-mix(in oklch,currentColor 18%,transparent)}
.re-tag-remove{--re-icon-ratio:.9}   /* the x is subordinate to the tag's own glyph */
.re-tag-remove svg{width:var(--re-icon);height:var(--re-icon)}

/* ── re-task-card — the card the board and the roadmap share ─────────────────────────
   Layout ONLY: it composes with re-card, it does not restate a surface. That is the v6
   §V6-1 lesson applied on purpose — re-row-list and re-card drifted apart precisely
   because each carried its own background, radius and shadow.
     <article class="re-card re-card-tap re-task-card">
   Title clamps at two lines: a board column is narrow and an unclamped title pushes the
   dates out of alignment across the column. */
.re-task-card{display:flex;flex-direction:column;gap:var(--re-space-2);padding:var(--re-space-3)}
.re-task-title{margin:0;font-size:calc(0.8125rem * var(--font-scale));font-weight:600;line-height:1.4;
  color:var(--foreground);display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.re-task-meta{display:flex;align-items:center;gap:var(--re-space-2);flex-wrap:wrap;min-width:0}
.re-task-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--re-space-2);min-width:0}
.re-task-due{display:inline-flex;align-items:center;gap:4px;flex-shrink:0;
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);font-variant-numeric:tabular-nums}
.re-task-due svg{width:var(--re-icon);height:var(--re-icon)}
.re-task-due-late{color:var(--danger)}
/* Drag APPEARANCE only. The gesture belongs to the app — the DS never sees a pointer
   event — but "which card am I holding" is a look, so it is defined once here instead of
   in every consumer. */
.re-task-card-dragging{transform:rotate(1.5deg);opacity:.85;box-shadow:var(--shadow-lg);cursor:grabbing}
@media (prefers-reduced-motion:reduce){.re-task-card-dragging{transform:none}}

/* ══ re-board — kanban: a strip of columns (v7 §V7-1) ═════════════════════════════════
   Two scrolls, at right angles, and they must not fight: the BOARD scrolls sideways
   through the columns, each COLUMN scrolls its own cards vertically. The page itself
   never scrolls in either direction.
   The column header is NOT sticky — it doesn't need to be. The column is a flex column
   holding a fixed header and a scrolling body, so the header stays put by construction.
   Sticky would have worked too and would have coupled the header to the scroll container;
   this way the two are independent.
   Grouping is a MARKUP decision: status, source, tag — the DS only knows "a column has a
   name and a count", so the same board serves 3 statuses or 5 accounts with no new class. */
.re-board{container-type:inline-size;   /* so the column width below can measure the BOARD, not the viewport */
  --re-board-col-w:min(300px,82cqw);   /* cqw, not vw: a board inside a narrower container (a docs demo, a split panel) would otherwise cut its own first column off */
  display:flex;align-items:stretch;gap:var(--re-space-3);
  height:100%;min-height:0;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x proximity;
  scroll-padding-inline:var(--re-gutter);   /* snap must respect the bleed gutter: with re-bleed-x the strip carries padding-inline, but scroll-snap-align:start snaps to the COLUMN edge and skips it — measured, the board opened at scrollLeft:20 with the first column flush to the screen edge, 20px out of line with the text above it. Harmless without the bleed (scrollLeft can't go negative). Same fix as re-chip-strip. */
  scrollbar-width:thin}
/* One class on the board tells the shell to stop scrolling and hand over its height —
   the same :has() contract the FAB and the tab bar use, so a consumer adds a class
   rather than re-plumbing overflow by hand. */
.re-board-fill{height:100%;min-height:0}
/* v8 §V8-3 — the :has() shortcut only fires when the board is a DIRECT child of re-main,
   and in a real shell it never is: a router draws into one root, the page header sits above
   it, and re-container carries the horizontal alignment, so the board ends up three or four
   levels deep. The selector then doesn't match, height:100% resolves to auto in an unbounded
   parent, and re-board-body{overflow-y:auto} never engages — the two-scroll model fails with
   NO symptom, because the board still renders and the page just scrolls instead. Worse, the
   column header then looks sticky (the column grew, the header is simply in flow), so a
   check for stickiness returns a false green.
   So the chain is NAMED instead of inferred: re-main-fill on re-main, re-fill on every
   wrapper in between. The :has() form stays for the direct-child case (docs demos), but the
   explicit chain is the one to reach for — it doesn't shorten the chain, it only hid it, and
   what it hid (every wrapper needs min-height:0) is something the consumer has to know. */
.re-main:has(> .re-board-fill),.re-main-fill{overflow:hidden;display:flex;flex-direction:column}
.re-board-col{flex:0 0 var(--re-board-col-w);scroll-snap-align:start;
  display:flex;flex-direction:column;min-height:0;max-height:100%;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-surface)}
.re-board-col-head{flex:none;display:flex;align-items:center;gap:var(--re-space-2);min-width:0;
  padding:var(--re-space-3);border-bottom:1px solid var(--border)}
.re-board-col-dot{width:8px;height:8px;flex-shrink:0;border-radius:50%;
  background:var(--_solid,var(--muted-foreground))}   /* group colour via re-c-{role} */
.re-board-col-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:calc(0.75rem * var(--font-scale));font-weight:600;color:var(--foreground)}
/* The count reads --_text, so re-c-danger on it is how a WIP limit says "over" — no
   separate limit component, and it tints with the dictionary like everything else. */
.re-board-col-count{flex-shrink:0;margin-left:auto;   /* pushes itself right, so the count sits at the edge whatever the heading is — re-board-col-title has flex:1 and does it on its own, but a tag-axis heading is a re-chip with no flex, and the count ended up glued to it */
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;
  color:var(--_text,var(--muted-foreground));font-variant-numeric:tabular-nums}
.re-board-body{flex:1;min-height:0;overflow-y:auto;
  display:flex;flex-direction:column;gap:var(--re-space-2);
  padding:var(--re-space-2) var(--re-space-3) var(--re-space-3);
  scrollbar-width:thin}
/* Items keep their height; the column scrolls. re-board-body is a scrolling flex column, and a flex item
   shrinks by default — harmless while an item's min-height is `auto` (its content), but re-card is
   overflow:hidden (v0.58.0), and overflow ≠ visible drops the automatic minimum to 0. From then on an
   overfull column crushed every card under its own content: measured by `tasks` on three axes, 12/12 ·
   21/21 · 23/23 cards at ~30px over 98px of content. tasks closed it locally (`.tk-card{flex-shrink:0}`);
   the rule belongs here, where the scrolling column is. */
.re-board-body > *{flex-shrink:0}
/* A column is 300px wide, so the full-size empty state (40px of padding) swallows it.
   Contextual, not a new class. */
.re-board-body .re-empty-state{padding:20px 12px;gap:var(--re-space-2)}
.re-board-add{flex:none;display:flex;align-items:center;justify-content:center;gap:6px;
  padding:var(--re-space-2);border:1px dashed var(--border);border-radius:var(--radius-control);
  background:none;color:var(--muted-foreground);cursor:pointer;
  font-family:inherit;font-size:calc(0.75rem * var(--font-scale));font-weight:500;
  transition:border-color .12s,color .12s,background .12s}
.re-board-add:hover{border-color:color-mix(in oklch,var(--brand) 40%,transparent);
  color:var(--foreground);background:color-mix(in oklch,var(--foreground) 3%,transparent)}
.re-board-add svg{width:var(--re-icon);height:var(--re-icon)}
/* Drop target — the LOOK only, like re-task-card-dragging. The gesture is the app's. */
.re-board-col-drop{border-color:color-mix(in oklch,var(--brand) 55%,transparent);
  background:color-mix(in oklab,var(--brand) 6%,var(--surface))}
.re-board-placeholder{flex:none;height:56px;border:1px dashed color-mix(in oklch,var(--brand) 45%,transparent);
  border-radius:var(--radius-md);background:color-mix(in oklch,var(--brand) 5%,transparent)}

/* ══ re-road — roadmap / timeline (v7 §V7-2) ══════════════════════════════════════════
   A row is an item, the horizontal axis is time. Three tokens drive everything, so the
   day / week / month scales are the SAME markup with a different unit width:
     --re-road-units     how many units the axis spans   (default 30)
     --re-road-unit-w    one unit's width                (default 40px)
     --re-road-label-w   the item-name column            (default 180px)
   Bars are placed with grid spans — `grid-column: 6 / span 4` — not with percentage
   offsets. Both need the consumer to turn dates into numbers, so the tie-breaker is that
   a span lands on the axis header EXACTLY, where a percentage drifts by a fraction of a
   unit and the drift grows with the number of units.
   The label column is sticky-left and the axis is sticky-top, so you never lose track of
   which row or which month you are looking at while scrolling in either direction. */
.re-road{--re-road-units:30;--re-road-unit-w:40px;--re-road-label-w:180px;
  height:100%;min-height:0;overflow:auto;
  background:var(--card);border:1px solid var(--border);border-radius:var(--radius-surface);
  scrollbar-width:thin}
/* width:max-content on the head, the body and every row — WITHOUT it they are only as wide
   as the scrollport, and a sticky child cannot escape its containing block: the label stuck
   for (scrollport - label) pixels and then got dragged along. Measured at 390px: scrolling
   200px moved the label 82px when it should have moved 0.
   ⚠ The desktop measurement passed and proved nothing — there the axis only overflowed by
   36px, so the label had nothing to be dragged by. A sticky test needs more scroll than the
   sticky element's own width to mean anything. */
.re-road-head{position:sticky;top:0;z-index:3;display:flex;width:max-content;min-width:100%;
  background:var(--card);border-bottom:1px solid var(--border)}
/* The corner has to be sticky on BOTH axes, or the axis labels scroll out from under it.
   position:sticky is ALSO the containing block for the absolutely positioned re-road-resize
   handle below — so do NOT add a second .re-road-head-label{position:relative} rule for it.
   That is exactly what happened in v0.14.0 and earlier: the later rule won at equal
   specificity and the corner stopped sticking (measured: it slid the full 300px of scroll
   while the row labels stayed put, so the bug read as "half of it works"). */
.re-road-head-label{position:sticky;left:0;z-index:1;flex:0 0 var(--re-road-label-w);
  padding:var(--re-space-2) var(--re-space-3);background:var(--card);
  border-right:1px solid var(--border);
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--muted-foreground)}
/* Widening the item column: grab the header divider, the way a table column resizes. The
   handle lives ONCE, in the corner, rather than on every row — it is the header divider that
   is draggable, and the rows follow because they all read --re-road-label-w.
   Same recipe as re-ss-resize (the second sidebar's edge): a 6px strip, col-resize, tinted on
   hover and while dragging. Kept as its own class because "ss" means second-sidebar and this
   is not one; if a third consumer turns up, the three should merge into one re-resize-col.
   The handle's containing block is the corner's own position:sticky — see above. */
.re-road-resize{position:absolute;right:-3px;top:0;bottom:0;width:6px;z-index:2;
  cursor:col-resize;touch-action:none;transition:background .15s}
.re-road-resize:hover,.re-road-resize.dragging{background:color-mix(in oklch,var(--primary) 25%,transparent)}
.re-road-axis{display:grid;grid-template-columns:repeat(var(--re-road-units),var(--re-road-unit-w))}
.re-road-unit{padding:var(--re-space-2) 0;text-align:center;
  border-left:1px solid color-mix(in oklch,var(--border) 60%,transparent);
  font-size:calc(0.625rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);
  font-variant-numeric:tabular-nums;white-space:nowrap;
  /* v7 — overflow:hidden, because nowrap alone let a too-long label ESCAPE the cell and land
     on its neighbour's text: measured at --re-road-unit-w:34px, "20 Ağu" ran 2.81px past its
     own cell and overlapped the next label by 1.81px. Two overlapping strings are unreadable
     and give no hint of the cause; a clipped one is at least visibly clipped. The label still
     has to be short enough — see the contract on the roadmap page. */
  overflow:hidden}
.re-road-unit-wide{font-size:calc(0.6875rem * var(--font-scale));color:var(--foreground)}  /* a month or quarter heading above the fine units */
.re-road-body{position:relative;width:max-content;min-width:100%}
.re-road-row{display:flex;align-items:stretch;width:max-content;min-width:100%;border-bottom:1px solid color-mix(in oklch,var(--border) 55%,transparent)}
.re-road-label{position:sticky;left:0;z-index:2;flex:0 0 var(--re-road-label-w);min-width:0;
  display:flex;align-items:center;gap:var(--re-space-2);
  padding:var(--re-space-2) var(--re-space-3);background:var(--card);
  border-right:1px solid var(--border);
  font-size:calc(0.75rem * var(--font-scale));color:var(--foreground)}
.re-road-label > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-road-track{display:grid;grid-template-columns:repeat(var(--re-road-units),var(--re-road-unit-w));
  align-items:center;min-height:40px;padding-block:5px}
.re-road-bar{grid-row:1;display:flex;align-items:center;min-width:0;height:24px;padding:0 9px;
  border-radius:var(--radius-full);
  background:var(--_tint,color-mix(in oklch,var(--brand) 18%,transparent));
  color:var(--_text,var(--foreground));
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
.re-road-bar-solid{background:var(--_solid,var(--primary));color:var(--_fg,var(--primary-foreground))}
/* A milestone is a single day, so it is a point rather than a span: a diamond centred in
   its own column. */
.re-road-milestone{grid-row:1;justify-self:center;width:13px;height:13px;
  background:var(--_solid,var(--brand));transform:rotate(45deg);border-radius:2px;cursor:pointer}
/* Today: absolutely placed instead of being a grid item, so it can cross every row without
   the label column and the rows having to share one grid. The label offset is included
   because the line lives in the body's coordinate space, and the sticky labels
   (z-index 2) deliberately paint OVER it. */
.re-road-today{position:absolute;top:0;bottom:0;z-index:1;width:0;pointer-events:none;
  left:calc(var(--re-road-label-w) + var(--re-road-today,0) * var(--re-road-unit-w));
  border-left:2px dashed color-mix(in oklch,var(--brand) 65%,transparent)}
/* A lane groups rows — status, source, assignee, whatever the grouping axis is.
   The BAR is full width so its background spans the timeline; the sticky is on its TEXT,
   because a sticky element that already fills its containing block has nowhere to stick and
   just scrolls away (measured: the heading rode -400px out of view). Hence the markup
   contract: the label goes in a child element, not as bare text in the lane. */
.re-road-lane{z-index:2;display:flex;align-items:center;gap:var(--re-space-2);
  width:max-content;min-width:100%;padding:var(--re-space-2) var(--re-space-3);
  background:var(--surface);border-bottom:1px solid var(--border);
  font-size:calc(0.6875rem * var(--font-scale));font-weight:600;text-transform:uppercase;
  letter-spacing:.03em;color:var(--muted-foreground)}
.re-road-lane > *{position:sticky;left:var(--re-space-3)}
/* An item with no dates CANNOT be invisible here. The band is a sibling of the scroller,
   not a row inside it: it is not on the time axis, and putting it inside would push it
   sideways out of view — the exact disappearance it exists to prevent. */
.re-road-undated{display:flex;align-items:flex-start;gap:var(--re-space-3);
  padding:var(--re-space-3);background:var(--surface);
  border:1px solid var(--border);border-radius:var(--radius-surface)}
.re-road-undated-label{flex:0 0 auto;font-size:calc(0.6875rem * var(--font-scale));font-weight:600;
  text-transform:uppercase;letter-spacing:.03em;color:var(--muted-foreground);padding-top:5px}
.re-road-undated-items{display:flex;flex-wrap:wrap;gap:var(--re-space-2);min-width:0}
/* Rows grow on touch so a bar can reach 44px without its target overlapping the rows above
   and below — raising the row is honest, overlapping targets steal taps. */
@media (pointer:coarse){.re-road-track{min-height:44px}}

/* ══ re-notes — variable-height note cards (v7 §V7-4) ═════════════════════════════════
   Notes are not equal-height cards, so re-card-stack is the wrong container: it assumes a
   uniform rhythm. This is a `columns` layout — no JS, no grid-row-span arithmetic, and it
   reflows at any width.
   ⚠ THE TRADE-OFF, stated rather than hidden: `columns` fills each column top to bottom, so
   the second item sits BELOW the first, not to its right. For notes sorted newest-first that
   reads fine, and pinned notes go in their own container above. If left-to-right order
   matters to you, that needs a grid with a computed row span per card, i.e. measuring
   content in JS — which is why it is not the default here.
   break-inside:avoid is not optional: without it a card splits across the column boundary. */
.re-notes{columns:var(--re-note-col-w,248px);column-gap:var(--re-space-3)}
/* ── re-notes-list — the wall's other view ──────────────────────────────────────
   A wall is for scanning by shape and colour; a list is for scanning by title, and the same
   notes have to work as both. So this is a MODIFIER on the container, not a second component:
   an app switching views must not re-author its items, which is the whole reason
   [board / roadmap / list] are alternatives on one task card rather than three card markups.

   One column in a shared frame with dividers, the same shape re-row-list draws — and the body
   clamp drops from six lines to two, because a row is scanned and a card is read. */
.re-notes-list{columns:auto;--re-note-lines:2;
  border:1px solid var(--border);border-radius:var(--radius-surface);background:var(--card);
  box-shadow:var(--shadow-sm);overflow:hidden}
/* The note keeps composing with re-card, so the card's own frame has to come off — otherwise
   the view is cards stacked inside a frame, which is neither of the two things it can be. */
/* Padding is NOT restated here: the base rule now sets it from --re-box-inset, and this rule
   used to carry a byte-identical copy. Only what DIFFERS in the list view stays. */
.re-notes-list > .re-note{margin-bottom:0;
  border:0;border-bottom:1px solid var(--border);border-radius:0;box-shadow:none}
.re-notes-list > .re-note:nth-last-child(1 of :not([hidden])){border-bottom:0}
/* A tinted note still tints — it just fills the row instead of a card, which is what makes
   the colour readable as a category in either view. */
/* Horizontal padding from --re-box-inset, not --re-space-3: a note is a BOX, and an
   re-section-label above it indents to --re-label-gutter, which is the box inset + the 1px
   border. With 15px of its own the note's text sat ~4px left of where the label expected it,
   and re-section-flush (which zeroes the gutter for a box with no inset at all) then
   overshot in the other direction. The vertical padding is the note's own rhythm and stays.
   (labs §V24-1) */
.re-note{break-inside:avoid;margin-bottom:var(--re-space-3);
  display:flex;flex-direction:column;gap:var(--re-space-2);
  padding:var(--re-space-3) var(--re-box-inset);
  /* One variable for colour, mixed INTO --card rather than replacing it, so a tinted note
     stays right in both themes — the palette scales are theme-independent, so
     background:var(--amber-50) would be a light note on a dark page.
     `in oklab`, NOT oklch: oklch is cylindrical, so mixing two colours interpolates HUE, and
     an achromatic endpoint (light --card is oklch(100% 0 0), hue undefined) is treated as
     hue 0 — every tint came out pink. Measured, amber 14% into the light card:
     oklch rgb(254,239,240) versus oklab rgb(253,243,227). */
  background:color-mix(in oklab,var(--re-note-hue,transparent) var(--re-note-mix,14%),var(--card))}
.re-note-head{display:flex;align-items:flex-start;gap:var(--re-space-2);min-width:0;
  --re-note-title-fs:calc(0.9375rem * var(--font-scale));--re-note-act:calc(28px * var(--density-mult,1))}
.re-note-title{flex:1;min-width:0;margin:0;
  font-size:var(--re-note-title-fs);font-weight:600;line-height:1.4;color:var(--foreground);
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.re-note-body{margin:0;font-size:calc(0.75rem * var(--font-scale));line-height:1.55;
  color:var(--muted-foreground);white-space:pre-line;
  display:-webkit-box;-webkit-line-clamp:var(--re-note-lines,6);line-clamp:var(--re-note-lines,6);
  -webkit-box-orient:vertical;overflow:hidden}
.re-note-foot{display:flex;align-items:center;flex-wrap:wrap;gap:var(--re-space-2);min-width:0}
.re-note-pin{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
  --re-note-pin-h:22px;--re-note-pin-icon:calc(var(--re-note-pin-h) * .64);
  width:var(--re-note-pin-h);height:var(--re-note-pin-h);padding:0;border:none;border-radius:50%;background:none;
  color:var(--muted-foreground);opacity:.6;cursor:pointer;
  font-family:inherit;font-size:inherit;transition:opacity .12s,background .12s,color .12s}
.re-note-pin:hover{opacity:1;background:color-mix(in oklch,var(--foreground) 8%,transparent)}
.re-note-pin.on{opacity:1;color:var(--brand)}
.re-note-pin svg{width:var(--re-note-pin-icon);height:var(--re-note-pin-icon)}
/* The head's controls are ONE pair: the pin and the card's ⋯ (a re-btn-icon, usually in a re-dropdown). They were a 22px
   circle beside a 32px rounded square, so their centres sat 5px apart and their hovers had two shapes (Ersin, notes
   2026-10-06: "pin ile ... buton align değil"). Same box, same radius, 2px apart, both centred on the title's FIRST
   line (the title clamps at two, so centring on the head would drift with it). An app that resizes the title sets
   --re-note-title-fs, not font-size, and the pair follows. */
.re-note-head > .re-note-pin{--re-note-pin-h:var(--re-note-act);border-radius:var(--radius-control);font-size:var(--re-ctl-sm-fs)}
/* The glyph the way a re-btn-sm draws its own: --re-icon resolved ON the svg (1em × ratio of the button's font). Read on
   the pin instead it took the pin's inherited 16px and drew 28px; a fixed half-box missed the ⋯ by 3px at compact. */
.re-note-head > .re-note-pin svg{width:var(--re-icon);height:var(--re-icon)}
.re-note-head > .re-note-pin,
.re-note-head > .re-btn-icon,
.re-note-head > .re-dropdown{flex-shrink:0;margin-top:calc((var(--re-note-title-fs) * 1.4 - var(--re-note-act)) / 2)}
.re-note-head > .re-btn-icon,
.re-note-head > .re-dropdown > .re-btn-icon{width:var(--re-note-act);height:var(--re-note-act);padding:0}
.re-note-head > .re-note-pin + .re-btn-icon,
.re-note-head > .re-note-pin + .re-dropdown{margin-left:calc(2px - var(--re-space-2))}
/* Unread (NO1 Focus, 2026-10-06): a note someone else changed since you last opened it. A brand dot before the title,
   on its first line; the same class on a list-view row's title does the same. */
.re-note-unread .re-note-title::before,.re-note-unread.re-row .re-row-title::before{content:"";display:inline-block;flex-shrink:0;
  width:7px;height:7px;margin-right:6px;border-radius:50%;background:var(--brand);vertical-align:middle;transform:translateY(-1px)}
/* The foot in two lines (Ersin, notes 2026-10-06): WHERE the note lives first, what it is ABOUT under it.
     re-note-meta   re-source (provider · account) · the list, the same re-source pill with a list icon · a tier badge
     re-note-tags   tags as small round badges (re-badge re-badge-sm), quieter than the line above
   Four badge shapes in one wrapping row read as noise. A foot with neither class keeps the old single row. */
.re-note-foot:has(> .re-note-meta, > .re-note-tags){flex-direction:column;align-items:flex-start;gap:6px}
.re-note-meta,.re-note-tags{display:flex;align-items:center;flex-wrap:wrap;min-width:0;max-width:100%}
.re-note-meta{gap:var(--re-space-1)}
.re-note-tags{gap:4px}
/* Pinned notes are their own container above the rest — a "pinned" flag inside one column
   flow would scatter them down the columns, which is the one thing pinning must not do. */
.re-notes-pinned{margin-bottom:var(--re-space-4)}
.re-section-block > .re-notes-pinned{margin-bottom:0}   /* in a section block the column gap owns the distance to the next section */

/* ══ re-cal — agenda and month (v7 §V7-5) ═════════════════════════════════════════════
   Two readings of the same data, and the near one comes first: an AGENDA is the next few
   days of flow, a MONTH is the long perspective over everything. So the agenda is not a
   reduced calendar — it is the default, and the month grid is the other view.
   The agenda is built ON re-timeline / re-slot, which already own the time rail, the `now`
   dot and the `past` fade. Nothing here re-implements them; this adds the day grouping, the
   all-day band, the event and the calendar's identity. */
.re-cal-day{display:flex;flex-direction:column}
/* The rail started flush against whatever line was above it — the day head's solid border, or
   the all-day band's dashed one — so the first event box looked glued to it. The day group owns
   that rhythm, not the timeline. */
.re-cal-day > .re-timeline{margin-top:var(--re-space-3)}
/* Sticky so the date stays readable while you scroll through several days. It sits on
   --background rather than --card: the heading belongs to the page, not to a card. *//* v10 §V10-1 — the sticky offset is a knob, because 0 is wrong in the DS's OWN canonical
   shell: in layouts/app-topbar.html the day head (z-index 2) and re-page-header (z-index 20)
   stick to the same line in the same scroller, and the header's opaque background hides it —
   measured at 390px, the day head sat 113px BEHIND the header, completely invisible. The
   consumer can't hardcode a number either: the header's height is title + subline + tabs,
   which moves with density and with a wrapped line, so theirs comes from a ResizeObserver.
   Same shape as --re-prose-size: the DS owns the slot, the consumer supplies the number. */

.re-cal-day-head{position:sticky;top:var(--re-cal-sticky-top,0);z-index:2;
  display:flex;align-items:baseline;gap:var(--re-space-2);
  padding:var(--re-space-2) 0;background:var(--background);
  border-bottom:1px solid var(--border)}
.re-cal-day-date{font-size:calc(0.9375rem * var(--font-scale));font-weight:700;color:var(--foreground);
  font-variant-numeric:tabular-nums}
.re-cal-day-name{font-size:calc(0.75rem * var(--font-scale));font-weight:600;text-transform:uppercase;
  letter-spacing:.03em;color:var(--muted-foreground)}
.re-cal-day.today .re-cal-day-date{color:var(--primary)}
/* All-day events have no place on a time rail, so they get their own band above it rather
   than being forced to 00:00 — which would read as "starts at midnight". */
.re-cal-allday{display:flex;flex-wrap:wrap;gap:var(--re-space-2);
  padding:var(--re-space-2) 0;border-bottom:1px dashed var(--border)}
/* One variable for the calendar's colour, mixed INTO --card in oklab — the same decision as
   re-note-hue, and for the same reason: a theme-independent palette step used as a background
   would be a pale card on a dark page, and oklch would rotate the hue to pink. */
.re-cal-event{display:flex;flex-direction:column;gap:3px;min-width:0;
  padding:var(--re-space-2) var(--re-space-3);
  border-left:3px solid var(--re-cal-hue,var(--brand));border-radius:var(--radius-inset);
  background:color-mix(in oklab,var(--re-cal-hue,var(--brand)) 10%,var(--card));
  cursor:pointer;transition:background .12s;
  --re-cal-event-hover:color-mix(in oklab,var(--re-cal-hue,var(--brand)) 18%,var(--card))}
/* The hover tint is a KNOB, and the week grid turns it off below. It is a knob rather than a
   deletion because the two views want opposite things and neither is wrong: an agenda is a
   LIST, where a row lighting under the pointer is the ordinary affordance; a week grid is a
   field of dozens of small boxes, where dragging the pointer across repaints each one in turn
   and the screen never settles. A consumer reported it as *"huzursuzluk"* — unease — which is
   a description of the grid, not of hover.
   Set --re-cal-event-hover to the event's own background to switch it off anywhere else, or
   back to a mix to switch it on in the week. What is NOT a knob is the week column's
   `z-index:20` on hover: that one is functional, it lifts an overlapped event so you can read
   the whole box, and removing it would take away the only way to read a stacked lane. */
.re-cal-event:hover{background:var(--re-cal-event-hover)}
/* Title row: the name on the left, WHICH CALENDAR on the right. The identity is the thing you
   scan for when several accounts share a screen, so it gets the corner rather than being
   buried after the time in the meta line. The title takes the rest and truncates. */
/* align-items:center, not flex-start: the title is white-space:nowrap, so it is ALWAYS one
   line and there is nothing for a top alignment to serve — against a 23px source pill an 18px
   line just sat high. (re-note-head keeps flex-start on purpose: its title clamps at two lines
   and the pin belongs at the top.) */
.re-cal-event-head{display:flex;align-items:center;gap:var(--re-space-2);min-width:0}
.re-cal-event-head > .re-cal-event-title{flex:1;min-width:0}
.re-cal-event-head > .re-source{flex-shrink:0}
.re-cal-event-title{font-size:calc(0.8125rem * var(--font-scale));font-weight:600;color:var(--foreground);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.re-cal-event-meta{display:flex;align-items:center;flex-wrap:wrap;gap:var(--re-space-2);min-width:0;
  font-size:calc(0.6875rem * var(--font-scale));color:var(--muted-foreground);font-variant-numeric:tabular-nums}
.re-cal-event-meta svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0}
.re-cal-event-declined{opacity:.55;text-decoration:line-through}
/* A day with nothing in it still has to say so — an agenda that silently skips empty days
   makes "nothing on Thursday" indistinguishable from "Thursday is missing". */
.re-cal-empty{padding:var(--re-space-3) 0;font-size:calc(0.75rem * var(--font-scale));
  color:var(--muted-foreground)}
/* ── month: the long view ── */
.re-cal-month{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));
  border:1px solid var(--border);border-radius:var(--radius-surface);overflow:hidden}
/* ── Filling a canvas ──────────────────────────────────────────────────────────────────
   All three grid readings take `re-fill` and stretch to the height they are given, so a
   consumer can draw one canvas (`container-lg`, height = the shell's) and switch views inside
   it without any of them changing size. Each does it the way its own shape allows:
   month shares the height across its WEEK ROWS, year across its rows of miniatures, and week
   already had it (its max-height is the fallback for when nobody hands it one).
   `minmax(0,1fr)` and not `1fr`: a bare 1fr floors at min-content, so a row with three chips
   in it would refuse to shrink and the grid would overflow instead of sharing. */
/* v45 (calendar, 2026-10-04): `repeat(auto-fit, minmax(0,1fr))` cannot count its repeats from a 0 floor, so it made
   one 1fr track, the other weeks fell into implicit auto rows and the spare height went to every auto track, the day
   names included (77px header, weeks 151·149·129·69·69). The header is the explicit row; every week is an implicit
   minmax(0,1fr) row, however many weeks the month has. */
.re-cal-month.re-fill{grid-template-rows:auto;grid-auto-rows:minmax(0,1fr)}
.re-cal-month.re-fill > .re-cal-cell{min-height:0;overflow:hidden}
/* Year in a box. The 4 × 3 model fits the window (Ersin: "4x3 ekrana sığan model. 3x4 ise scroll edilen model"),
   so it fills only at 4 columns on a window at least 760px tall; four rows at 3 columns drew 12px cells (calendar,
   900 × 900). Elsewhere the year keeps natural cards and scrolls inside its box.

   Filling, the month takes the whole card, both ways (v48, Ersin: "kutular kare tutulmasın; alabileceği minimum bir
   text size, width, height olsun, onun üstüne genişlesin, yükselsin"). v0.87.0 kept the month square and left half a
   wide card empty (168 of 333px at 1700 × 1000), and too much padding above the name and under the last week. Now the
   columns and week rows are minmax(<floor>, 1fr): a cell is at least 18 × 14px with a 10px numeral and grows from there;
   the numeral follows the grid's shorter side. The card's padding and the name's gap are tighter in this mode. Below
   the floor the window is in the scrolling model already (the media query's thresholds). */
@media (min-width:1024px) and (min-height:760px){
  .re-cal-year.re-fill{grid-auto-rows:minmax(0,1fr);align-content:stretch}
  .re-cal-year.re-fill > .re-cal-year-month{min-height:0;overflow:hidden;
    padding:var(--re-space-2) var(--re-space-3);gap:var(--re-space-1)}
  .re-cal-year.re-fill .re-cal-year-grid{flex:1;min-height:0;container-type:size;
    grid-template-columns:repeat(7,minmax(18px,1fr));grid-template-rows:auto repeat(6,minmax(14px,1fr))}
  .re-cal-year.re-fill .re-cal-year-day{aspect-ratio:auto;min-height:0;
    font-size:calc(clamp(10px, 6.5cqmin, 16px) * var(--font-scale))}
}
@media (max-width:1023px),(max-height:759px){
  /* max-content, not auto: inside a box of definite height an auto row floors at the card's min-height (0 here)
     and the cards came out 33px (measured). */
  .re-cal-year.re-fill{grid-auto-rows:max-content;align-content:start;overflow-y:auto}
}
.re-cal-dow{padding:var(--re-space-2);text-align:center;background:var(--surface);
  border-bottom:1px solid var(--border);
  font-size:calc(0.625rem * var(--font-scale));font-weight:600;text-transform:uppercase;
  letter-spacing:.04em;color:var(--muted-foreground)}
.re-cal-cell{display:flex;flex-direction:column;gap:3px;min-height:78px;padding:5px;
  border-right:1px solid color-mix(in oklab,var(--border) 55%,transparent);
  border-bottom:1px solid color-mix(in oklab,var(--border) 55%,transparent)}
.re-cal-cell:nth-child(7n){border-right:none}
.re-cal-cell-num{font-size:calc(0.6875rem * var(--font-scale));font-weight:600;color:var(--foreground);
  font-variant-numeric:tabular-nums}
.re-cal-cell-out .re-cal-cell-num{color:var(--muted-foreground);opacity:.55}   /* a day from the neighbouring month */
/* align-self:start is the whole fix: the cell is a flex COLUMN, so a span stretches to the
   full cell width by default and a 19px-tall circle became a 130px-wide oval. */
/* Same ratio as the week's disc — see --re-cal-today-ratio. It was 19px here and 22px there
   against 11px and 14px type, i.e. two numbers pretending to be one design. 1.72em lands at
   18.9px here (was 19) and 24.1px there (was 22, and too tight). */
.re-cal-cell.today .re-cal-cell-num{display:inline-flex;align-self:start;
  align-items:center;justify-content:center;
  min-width:calc(1em * var(--re-cal-today-ratio));height:calc(1em * var(--re-cal-today-ratio));
  border-radius:50%;background:var(--primary);color:var(--primary-foreground)}
/* In a month cell there is no room for a card, so an event becomes one line. */
.re-cal-chip{display:flex;align-items:center;gap:5px;min-width:0;padding:1px 4px;border-radius:3px;
  background:color-mix(in oklab,var(--re-cal-hue,var(--brand)) 14%,var(--card));
  font-size:calc(0.625rem * var(--font-scale));color:var(--foreground);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
.re-cal-chip::before{content:"";width:5px;height:5px;flex-shrink:0;border-radius:50%;
  background:var(--re-cal-hue,var(--brand))}
.re-cal-more{font-size:calc(0.625rem * var(--font-scale));font-weight:600;color:var(--muted-foreground);
  background:none;border:none;padding:1px 4px;text-align:left;cursor:pointer;font-family:inherit}

/* ── week: the seven-column time grid (labs §V28-1) ────────────────────────────────────
   Three rows have to agree on eight columns — the day heads, the all-day band and the body.
   ONE grid-template-columns declaration serves all three, because "the same numbers written
   three times" is the defect this file has been caught by more than any other. Retune the
   week from --re-cal-rail-w / --re-cal-col-min / --re-cal-hour-h in base.css.

   The columns have a FLOOR, not a fraction: below --re-cal-col-min an event title has no
   room at all, so a phone scrolls the week sideways instead of showing seven unreadable
   slivers. 7 x 92 + 52 = 696px, so a phone scrolls and a tablet does not.

   And the event is THE SAME COMPONENT as in the agenda. re-cal-event is not re-declared
   here; the COLUMN positions what it holds, from two numbers the consumer supplies
   (--re-cal-start / --re-cal-end in decimal hours). Same markup reads in three views — that
   is the whole reason this belongs in the DS and not in a consumer. */
/* clip-path, because border-radius does NOT clip a scrollbar. Measured at 8x on the corner:
   the 4px thumb was painted straight over the border arc and outside it — a rounded frame with
   a straight grey bar cutting its corner. The usual fix is a wrapper (radius + overflow:hidden
   outside, scroller inside), but this component's markup is a published contract, and
   clip-path does the same job with no extra element: verified that the thumb is clipped and
   that position:sticky still works (the head's offset is 1.0px with and without it).
   ⚠ Do NOT copy this onto re-main. clip-path makes the element a containing block for
   position:fixed descendants, and re-main hosts modals, sheets and the FAB — they would be
   trapped inside the scroller. re-main has no radius, so it never needed it. */
.re-cal-week{display:flex;flex-direction:column;overflow:auto;
  border:1px solid var(--border);border-radius:var(--radius-surface);
  clip-path:inset(0 round var(--radius-surface));
  background:var(--card)}
/* Vertical scrolling is INTERNAL, so the day heads can stay put — which is the entire point
   of a week grid. Height by default, or hand it the real one: re-cal-week.re-fill inside the
   re-main-fill / re-fill chain (see shell.mdx) takes the shell's height instead of a guess. */
.re-cal-week:not(.re-fill){max-height:var(--re-cal-week-h,72vh)}
:is(.re-cal-week-head,.re-cal-week-allday,.re-cal-week-body){
  display:grid;grid-template-columns:var(--re-cal-rail-w) repeat(7,minmax(var(--re-cal-col-min),1fr));
  /* The row's BOX must be as wide as its columns, or a background paints only part of it.
     Each row is a block child of the scroller, so `width:auto` = the scroller's content width
     (measured 276px at 390px) while the columns overflow to 696 — and a background paints the
     BOX. Symptom: scroll the week sideways on a phone and the day-head strip has --surface for
     the first 276px and nothing after it. ⚠ The check I had written could not see this: it
     compared the three rows' COLUMN OFFSETS, and all three were wrong by the same amount, so
     they agreed perfectly. `min-width` and not `width:max-content`: max-content would let
     `1fr` resolve to each column's own content and the seven days would stop being equal. */
  min-width:calc(var(--re-cal-rail-w) + 7 * var(--re-cal-col-min))}
/* Sticky against THIS box, not re-main: the week owns its own scrollport (above), so the
   offset is 0 and there is no --re-cal-sticky-top to supply. The agenda's day head is the
   opposite case and that is why it has the knob. */
.re-cal-week-head{position:sticky;top:0;z-index:3;background:var(--surface);
  border-bottom:1px solid var(--border)}
/* position:relative so a consumer can place a marker in the cell — a dot for "has events",
   a count — without wrapping the heading. Reported as missing (labs §V32-5). */
.re-cal-wday{position:relative;display:flex;flex-direction:column;align-items:center;gap:1px;
  padding:var(--re-space-2) var(--re-space-1);min-width:0;
  border-left:1px solid color-mix(in oklab,var(--border) 55%,transparent)}
.re-cal-wday-name{font-size:calc(0.625rem * var(--font-scale));font-weight:600;
  text-transform:uppercase;letter-spacing:.04em;color:var(--muted-foreground)}
.re-cal-wday-date{font-size:calc(0.875rem * var(--font-scale));font-weight:700;color:var(--foreground);
  font-variant-numeric:tabular-nums}
/* Today reads the same way it does in the month grid — a filled disc on the number, not a
   tinted column. Two views, one signal. */
.re-cal-wday.today .re-cal-wday-date{display:inline-flex;align-items:center;justify-content:center;
  min-width:calc(1em * var(--re-cal-today-ratio));height:calc(1em * var(--re-cal-today-ratio));
  border-radius:50%;background:var(--primary);color:var(--primary-foreground)}
/* `sel` is the day the USER picked; `today` is a fact about the clock. Two different things,
   so two different marks that can be true at once: today FILLS the disc, sel RINGS it. A
   second fill would have made "today, and also selected" unreadable. (labs §V32-5) */
.re-cal-wday.sel .re-cal-wday-date{display:inline-flex;align-items:center;justify-content:center;
  min-width:calc(1em * var(--re-cal-today-ratio));height:calc(1em * var(--re-cal-today-ratio));
  border-radius:50%;box-shadow:inset 0 0 0 2px var(--primary);color:var(--primary)}
.re-cal-wday.today.sel .re-cal-wday-date{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--primary)}
.re-cal-wday.sel{background:color-mix(in oklab,var(--primary) 7%,transparent)}
/* The all-day band, week form. The agenda's re-cal-allday is one flex row for one day; here
   each day needs its own cell, so the band is a row of the same eight columns. */
.re-cal-week-allday{border-bottom:1px dashed var(--border)}
.re-cal-week-band{display:flex;flex-direction:column;gap:3px;min-width:0;padding:var(--re-space-2) 4px;
  border-left:1px solid color-mix(in oklab,var(--border) 55%,transparent)}
/* The rail: one label per hour, each exactly one hour tall, text at the TOP so "09:00" sits
   on its own gridline rather than floating between two. */
.re-cal-week-rail,.re-cal-week-rail-label{position:sticky;left:0;z-index:2;
  background:var(--card);border-right:1px solid var(--border)}
.re-cal-week-head > .re-cal-week-rail-label{background:var(--surface);z-index:4}
.re-cal-week-rail-label{display:flex;align-items:center;justify-content:flex-end;
  padding:var(--re-space-2) var(--re-space-2);
  font-size:calc(0.625rem * var(--font-scale));font-weight:600;text-transform:uppercase;
  letter-spacing:.04em;color:var(--muted-foreground);text-align:right}
.re-cal-hour{height:var(--re-cal-hour-h);padding-right:var(--re-space-2);text-align:right;
  font-size:calc(0.6875rem * var(--font-scale));color:var(--muted-foreground);
  font-variant-numeric:tabular-nums;line-height:1.1}
/* The body. position:relative for the now line, which spans the seven columns and therefore
   cannot be a grid item in one of them. */
.re-cal-week-body{position:relative}
/* The hour lines are ONE declaration, not N elements: a repeating gradient at --re-cal-hour-h.
   An hour row per line would put 15 empty divs x 7 columns in the DOM for a ruler. */
.re-cal-week-col{position:relative;min-width:0;
  min-height:calc((var(--re-cal-to,24) - var(--re-cal-from,0)) * var(--re-cal-hour-h));
  border-left:1px solid color-mix(in oklab,var(--border) 55%,transparent);
  background:repeating-linear-gradient(to bottom,
    color-mix(in oklab,var(--border) 55%,transparent) 0 1px,
    transparent 1px var(--re-cal-hour-h))}
.re-cal-week-col.today{background-color:color-mix(in oklab,var(--primary) 5%,transparent)}
/* The column places the event; the event stays itself. --re-cal-lane / --re-cal-lanes are the
   overlap answer: the consumer is the only side that can work out that three events collide,
   so it supplies the two integers and the DS does the arithmetic. Default 0/1 = full width. */
/* z-index above the now line (which is 1): the line spans the week, so on a column that has
   an event it ran straight across the title and read as a strikethrough. The line still shows
   in every empty part of the row, which is where it is doing its job. */
/* container-type:size — the box queries ITSELF below, on both axes. That is what lets the DS
   decide the layout from the real rendering instead of asking the consumer for a class: a
   30-minute box is 22px, and a 60-minute one sharing a two-lane column is 44px tall but half
   as wide, and only the box knows both numbers. (This replaces re-cal-event-tall.)
   Vertical centring is align-content:center on the rule below, NOT justify-content. This block
   sets flex-direction:row, so the main axis is horizontal and justify-content:center centres
   the TEXT in the box — which is what it did, and every title came out mid-column and
   unreadable. The axis flipped under the property when the direction changed; the property
   itself never moved. */
.re-cal-week-col > .re-cal-event{position:absolute;overflow:hidden;z-index:2;
  container-type:size;
  top:calc((var(--re-cal-start,0) - var(--re-cal-from,0)) * var(--re-cal-hour-h));
  height:calc((var(--re-cal-end,1) - var(--re-cal-start,0)) * var(--re-cal-hour-h));
  /* OVERLAP, not an equal split. Splitting a column between N events divides the width by N,
     and at N=2 in a one-lane column that is 49px — measured, the titles rendered as "D…" and
     "S…" and neither event was identifiable, which is the whole job of the box. Every calendar
     app converges on the same answer: each event keeps most of the width, later ones are
     inset from the left and stack on top, so the first line of every title stays readable and
     the pile is legible as a pile.
     --re-cal-lane is still the consumer's integer (it is the only side that can work out who
     collides); --re-cal-lanes is no longer read for the width and is kept accepted so existing
     markup does not break. The inset is a share of the column, not a pixel, so it holds at
     every column width. */
  left:calc(var(--re-cal-lane,0) * var(--re-cal-lane-inset,22%));
  right:2px;margin-left:2px;padding:2px var(--re-space-2);line-height:1.15;
  /* A floor, because the height is proportional and has none: at 44px/hour a 15-minute event
     is 11px and its TITLE was cut (gap v39 §V39-2). 27px is one line plus padding, found by
     bisecting on the box that clipped most (none 16 · 23 → 4 · 25 → 2 · 27 → 0). The cost is
     written down and accepted: a short event now runs into the slot below it, as it does in
     every calendar app. Lanes are unaffected — the consumer computes them from hours, not
     pixels. A token so a denser grid can lower it. */
  min-height:var(--re-cal-event-min-h,27px);
  z-index:calc(2 + var(--re-cal-lane,0))}
/* A stacked event needs an edge, or two tints of the same hue read as one box. Left only —
   that is the side the box beneath shows through.
   ⚠ The offset is ARITHMETIC on the lane, not a selector on it: `:not([style*="--re-cal-lane:0"])`
   was the first version and it is the same fragile shape this file removed from
   re-cal-year-day — it reads the inline-style STRING and breaks on a space. At lane 0 the
   multiplication gives 0 and the shadow is simply not there. */
.re-cal-week-col > .re-cal-event{box-shadow:calc(var(--re-cal-lane,0) * -2px) 0 0 0 var(--card)}
/* Hover brings a box FORWARD, which is what the overlap needs: a stacked event is partly
   under its neighbour, and the one you are pointing at should be the whole one. 20 clears any
   lane (lanes are 2 + n) without reaching the sticky rail's layer. */
.re-cal-week-col > .re-cal-event:hover{z-index:20}
/* In a week column the event is SCANNED, not read: 12px at 500 rather than the agenda card's
   13px at 600. The agenda keeps the heavier setting — there the card IS the content. */
.re-cal-week-col > .re-cal-event > .re-cal-event-head > .re-cal-event-title{
  font-size:calc(0.75rem * var(--font-scale));font-weight:500}
.re-cal-week-col > .re-cal-event > .re-cal-event-meta{
  font-size:calc(0.6875rem * var(--font-scale));white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;flex-wrap:nowrap}
/* One flex line by default, a second one when the box can carry it — and the switch is made
   with CHILD properties only, because a container query CANNOT style its own container: a
   `@container` block on .re-cal-event reaches its descendants, never `flex-direction` on the
   element itself. That is why the meta gets `flex-basis:100%` rather than the event getting
   `flex-direction:column`; it took a wrong first pass to notice.

   Default (short OR narrow): title then `, 09:00–09:30` on one line, both clipped in that
   order — a long title keeps the line and pushes the time out, which is where every calendar
   app lands. The `, ` is generated, not written into the markup, because the same element is a
   second LINE in a tall box. */
/* NOT flex here — a BLOCK with inline children, and the reason is that the switch has to be
   made from the CHILDREN. A container query cannot style its own container, so `flex-wrap`
   (a container property) can never be flipped by the box's own query; two passes were spent
   discovering that twice. `display` on a child can.

   So the short form is text flow: title and time are inline and run on ONE line, clipped in
   reading order. Flex wrapping was the previous attempt and it is decided by WIDTH — a 22px
   box has no room for a second line at any width, so the meta wrapped and came back as a
   half-clipped row with a leading comma hanging under the title.

   The `, ` is generated, not written into the markup, because the same element becomes a
   second LINE in a tall box.

   ⚠ There was an `align-content:center` here and it is gone, because its own comment named the
   case where it does nothing. The claim was *"one line sits on the box's middle at 30
   minutes"*. Measured, same box, toggling only that property — distance from the box's top to
   the title's first line:

       box               center    start
       132px (3 h)        53.5      5.0     ← 48.5px of push
        66px (1.5 h)      20.5      5.0
        22px (30 min)      5.0      5.0     ← the cited case: difference ZERO

   At 30 minutes the content fills the box, so there is no free space to distribute and the
   property cannot act. Its only measurable effect was in tall boxes, pushing the title away
   from the hour the event starts at — which is the opposite of what a time grid is read for,
   and what a consumer reported. A justification that names the case where the rule is inert
   is not a weak justification, it is evidence the rule outlived its context: this block was
   `display:flex` once (see the axis note above), and centring may well have earned its place
   there. */
.re-cal-week-col > .re-cal-event{display:block;
  white-space:nowrap;text-overflow:ellipsis;
  --re-cal-event-hover:color-mix(in oklab,var(--re-cal-hue,var(--brand)) 10%,var(--card))}
.re-cal-week-col > .re-cal-event > .re-cal-event-head{display:inline}
.re-cal-week-col > .re-cal-event > .re-cal-event-head > .re-cal-event-title{display:inline}
.re-cal-week-col > .re-cal-event > .re-cal-event-meta{display:inline}
.re-cal-week-col > .re-cal-event > .re-cal-event-meta::before{content:", "}
/* The source pill does not ride in a week box. It has re-badge's metrics — 23px at the default
   density, 25.7 at comfortable — and the box's floor is one TEXT line, 23px of content: the pill
   filled it exactly and overflowed it at comfortable (labs v39 follow-up, `calendar` measured
   up to 9px). In the grid the calendar is already named by the box's hue (--re-cal-hue), and on
   a ~150px column the pill took the title's width. What must survive is a BROKEN sync — silence
   there reads a partial list as complete — so re-source-error stays, as its own 6px dot. The
   pill's title attribute stays on the dot, so hovering it still says which calendar and why. */
.re-cal-week-col > .re-cal-event .re-source{display:none}
.re-cal-week-col > .re-cal-event .re-source-error{display:inline-block;width:6px;height:6px;
  padding:0;border:0;border-radius:50%;background:var(--danger);font-size:0;
  vertical-align:middle;margin-inline-start:var(--re-space-1)}
.re-cal-week-col > .re-cal-event .re-source-error > *,
.re-cal-week-col > .re-cal-event .re-source-error::after{display:none}
/* No 44px tap layer in a week column. The coarse-pointer block gives every re-cal-event a ::before
   of height max(100%,44px), and in a week box it did two wrong things (`calendar` found it,
   measured here with a touch context): the box is overflow:hidden, so the layer was CLIPPED and
   the hit area stayed the box — 5px above or below a 27px box hit the column, not the event —
   while the layer still counted in scrollHeight (27px box → 36), which read as 9px of clipped
   text in the D4 measurement. And it should not grow here anyway: in a time grid the box's
   height IS its time, and a taller target would take taps from the event or the empty slot
   next to it. The floor (--re-cal-event-min-h) is the week's answer to a small target. */
@media (pointer: coarse){
  .re-cal-week-col > .re-cal-event::before{content:none}
}
/* Tall AND wide enough for two lines.
   40px = one 12px line + one 11px line + the box's padding.
   72px is what a time range needs on its own line at 11px type ("10:00–11:30", measured).

   ⚠ A container size query measures the CONTENT box, not the border box, and that cost an hour:
   the first threshold was 90px, derived from the event's 100px border-box width, while the
   engine was comparing against 77px of content (100.3 − 3px lane gutter − 2×10px padding).
   The width half never matched, so the COMBINED query never matched — and because each half
   was tested alone and the height half worked, it read as "`and` is broken in container
   queries". It is not; the width half was simply always false. Binary-searching the threshold
   (1 / 10 / 20 / 40 / 60 matched, 80 / 90 / 100 did not) is what showed the real number. */
/* ⚠ Same selector DEPTH as the default rules above, and that is not style. A container query
   does not add specificity — it only gates when a rule applies — so `.re-cal-event-meta`
   (0,1,0) inside the query lost to `.re-cal-week-col > .re-cal-event > .re-cal-event-meta`
   (0,3,0) outside it. The query matched and the declarations were simply overridden, which
   looks exactly like a query that does not match. */
/* 37px of CONTENT box (a container size query measures content, not the border box).
   ⚠ It was 26px, derived from the two font sizes added up (13.8 + 12.65 = 26.5) — but two lines
   also carry their line boxes and the gap between them, and the measured need is 36–38px
   (gap v39 §V39-1, `calendar`). At 26 a 45-minute box (29px of content) went to two lines and
   clipped 7–9px; at 37 it stays on one line, "title, 15:30–16:15", and a 60-minute box (40px)
   takes two. A size query cannot read a custom property, so this stays a literal. */
@container (min-height: 37px) {
  .re-cal-week-col > .re-cal-event > .re-cal-event-meta{display:block}
  .re-cal-week-col > .re-cal-event > .re-cal-event-meta::before{content:none}
}

/* Now: --primary, the same signal re-slot.now uses. One concept must not be two colours in
   two views of the same data. */
/* left starts at the rail's edge rather than 0: the sticky rail has a background and a higher
   z-index, so a line drawn under it is invisible either way — but then the geometry is only
   correct BECAUSE of a stacking order, and the next person to retune z-index breaks it
   without touching this rule. Say where the line begins. */
.re-cal-week-now{position:absolute;left:var(--re-cal-rail-w);right:0;height:0;z-index:1;pointer-events:none;
  top:calc((var(--re-cal-now,0) - var(--re-cal-from,0)) * var(--re-cal-hour-h));
  border-top:2px solid var(--primary)}
.re-cal-week-now::before{content:"";position:absolute;left:-4px;top:-5px;
  width:8px;height:8px;border-radius:50%;background:var(--primary)}

/* ── re-cal-preview — what an event says when you tap it ───────────────────────────────
   A week box holds a title and maybe a time. Everything else — where, who, which calendar,
   what to do about it — needs somewhere to go, and sending a tap straight to a detail PAGE
   loses the grid you were reading. So: a preview first, the page on Edit.

   It is re-modal's machinery, not a second overlay: the same backdrop, the same z-index, the
   same Escape handling. What this adds is the SHAPE — an actions row at the top rather than a
   title bar, because the title belongs with the time and the source, and a preview is read in
   one glance before it is acted on.
   Narrower than re-modal (420 vs 480) and it does not stretch: a preview that fills the screen
   has stopped being a preview. */
.re-cal-preview{width:min(420px,94vw)}
.re-cal-preview > .re-cal-preview-actions{display:flex;align-items:center;justify-content:flex-end;
  gap:var(--re-space-1);padding:var(--re-space-2) var(--re-space-2) 0}
/* No hue rail. It was here to make the panel read as THAT event, and in the panel it reads as
   decoration instead: the event box needs the rail because a week column is a wall of boxes
   and the colour is the only thing separating them — a modal has no neighbours to be told
   apart from. --re-cal-hue is still inherited, for anything inside that wants it. */
.re-cal-preview > .re-modal-body{padding-top:var(--re-space-2)}
/* overflow:visible, against re-modal's auto. A tooltip on the action row is absolutely
   positioned ABOVE its button, i.e. outside the panel's box, and a scrolling ancestor clips
   it — the label came out cut off inside the frame. A preview is short by design; if yours is
   long enough to need scrolling, it has stopped being a preview. */
.re-cal-preview{overflow:visible}
.re-cal-preview-title{margin:0 0 2px;font-size:calc(1.125rem * var(--font-scale));font-weight:700;
  line-height:1.3;color:var(--foreground)}
.re-cal-preview-when{font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground);
  font-variant-numeric:tabular-nums}
/* One row per fact, icon in a fixed gutter so the values line up — the same column idea
   re-section-label uses, at a smaller scale. */
.re-cal-preview-meta{display:flex;flex-direction:column;gap:var(--re-space-2);
  margin-top:var(--re-space-3)}
.re-cal-preview-line{display:flex;align-items:flex-start;gap:var(--re-space-2);
  font-size:calc(0.8125rem * var(--font-scale));color:var(--foreground)}
.re-cal-preview-line > svg{width:var(--re-icon);height:var(--re-icon);flex-shrink:0;
  margin-top:2px;color:var(--muted-foreground)}

/* ── year: twelve months at a glance (labs §V28-1) ─────────────────────────────────────
   The year is not a calendar you read dates off — at this size a day is 12px and the number
   inside it is decoration. It answers one question: WHERE IS IT BUSY. So the day carries an
   intensity, --re-cal-level (0-4), and the month caption is the tap target that takes you to
   the month grid. A year view that tried to show event titles would be a worse month view. */
/* Twelve months in columns that divide twelve: 4 × 3 → 3 × 4 → 2 × 6 → 1 × 12, never auto-fill (v47, Ersin
   2026-10-04: "1280'de 4×3 ise 1280+ 4×3 olsun … tablet dikeyden sonra 3×4, sonra 2×6, belki iPhone dikey 1×12";
   auto-fill drew 7 × 2 with two empty slots at 2000px and 6 × 2 at 1600). January top-left, December bottom-right,
   every row full, at every width. */
.re-cal-year{display:grid;gap:var(--re-space-4);grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:1023px){.re-cal-year{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:699px){.re-cal-year{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:479px){.re-cal-year{grid-template-columns:minmax(0,1fr)}}
.re-cal-year-month{display:flex;flex-direction:column;gap:var(--re-space-2);
  padding:var(--re-space-3);border:1px solid var(--border);border-radius:var(--radius-inset);
  background:var(--card)}
.re-cal-year-name{font-size:calc(0.8125rem * var(--font-scale));font-weight:700;color:var(--foreground)}
.re-cal-year-month.today .re-cal-year-name{color:var(--primary)}
.re-cal-year-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;container-type:inline-size}
.re-cal-year-dow{font-size:calc(0.5625rem * var(--font-scale));font-weight:600;
  color:var(--muted-foreground);text-align:center;padding-bottom:2px}
/* aspect-ratio, not a height: the cell follows the column width, so the miniature stays a
   square grid at every card size and at every density without a second number.

   The cell DOES carry its day number. An earlier version dropped it, on the argument that a
   9px numeral in a tint is noise and that the contrast has no good answer — and the second
   half of that was only true of the ramp it was written for. A year view whose cells are
   anonymous cannot answer "which weekday is the 1st of July", which is a real reason to open
   a year. So the number stays and the ramp is chosen to keep it legible instead:

     background   --muted at level 0, then 14% of --brand per level (max 56%)
     colour       --muted-foreground at level 0, mixed toward --foreground with the level

   Both ends move together, so the pair is contrast-checked at every step rather than at one.
   ⚠ Do not branch on the level with a selector: the version that did used
   `:not([style*="--re-cal-level:0"])`, which reads the inline-style STRING and breaks on a
   space. A custom property can be arithmetic in a colour; it cannot be a condition. */
.re-cal-year-day{aspect-ratio:1;display:flex;align-items:center;justify-content:center;
  border-radius:2px;line-height:1;
  /* The numeral grows with the miniature (v47: Apple and Google scale the month, the cell stays square and the spare
     room goes to the type): about half a cell (a cell is 1/7 of the grid), 9px floor, 15px ceiling. */
  font-size:calc(clamp(9px, 6.6cqw, 15px) * var(--font-scale));font-weight:500;font-variant-numeric:tabular-nums;
  background:color-mix(in oklab,var(--brand) calc(var(--re-cal-level,0) * 14%),var(--muted));
  /* 15% + 21%/level, not 0% + 25%: at a pure --muted-foreground the level-0 number measured
     4.19:1 against its own --muted ground — that pair is the DS's, and it is under 4.5 wherever
     it is used as small text. The offset lifts the quietest step over the line without moving
     the busy end. Measured over the card's real ground (1x1 canvas composite, not against
     black), light: 5.30 / 5.94 / 6.51 / 6.68 / 6.72; dark: 6.17 / 6.25 / 6.04 / 5.60 / 5.37.
     Ten values, all over 4.5, and the flattest curve is the dark one — which is the point of
     moving both ends together instead of picking a text colour once. */
  color:color-mix(in oklab,var(--muted-foreground),
                  var(--foreground) calc(15% + var(--re-cal-level,0) * 21%))}
/* A leading/trailing blank keeps the week columns true. `visibility:hidden` and not `opacity`
   or a transparent background: the cell must take NO ground at all, or an empty Monday reads
   as a day with nothing on it instead of as "this month does not start here". */
.re-cal-year-day-out{visibility:hidden}
/* Today's ring is drawn inside the cell, and an inside ring's inner corner is the outer radius minus its width: 2px − 2px
   left a square hole in a rounded cell (Ersin, 2026-10-04: "aktif günün kalın border'ı içinde de radius olsun"). The
   outer radius grows by the ring's width, so the inner edge keeps the 2px every other cell has. Inset box-shadow, not
   outline: the shadow follows the radius on every engine. */
.re-cal-year-day.today{border-radius:4px;box-shadow:inset 0 0 0 2px var(--primary);
  color:var(--primary);font-weight:700}

/* ══ WHY THIS BLOCK IS LAST IN THE FILE ══════════════════════════════════════════════
   It is a cross-cutting OVERRIDE: it re-declares font-size and position on components
   defined elsewhere, at the same specificity (0,1,0) and in the same layer. So source
   order decides, and anything defined BELOW it silently wins.
   That is not hypothetical — it happened: re-tag-input-field was added further down the
   file, matched the coarse rule by name, and still measured 13px on touch, because its
   own base rule came later. Nothing errored; the field just kept zooming iOS.
   Keeping the block at the very end makes coverage a property of the file's shape
   instead of something every future component has to remember.
   ══════════════════════════════════════════════════════════════════════════════════════ */
/* ══ TOUCH INPUT — v6 Faz 1 (V6-5 dokunma hedefi · V6-6 odak yakınlaştırması · V6-8 çift dokunma) ══
   Bu blok yalnız DOKUNMALI işaretçide devreye giriyor: `(pointer: coarse)`. Fare/trackpad
   ile gelen ziyaretçi DS'in kompakt ölçeğini olduğu gibi görüyor — hiçbir masaüstü ölçüsü
   değişmiyor.
   ⚠ ÖLÇÜM NOTU: bu üç maddenin belirtisi yalnız WebKit'te çıkıyor ve headless Chrome'da
   TEMİZ görünüyor. Kabul ölçümü gerçek iOS Safari/Brave olmadan yapılmış sayılmaz.
   ⚠ `Emulation.setDeviceMetricsOverride({mobile:true})` TEK BAŞINA `(pointer: coarse)`ı
   eşleştirmiyor; medya özelliği ayrıca override edilmeli, yoksa kural sınanmamış kalır.
   ────────────────────────────────────────────────────────────────────────────────────── */

/* V6-8 — çift dokunma yakınlaştırmasını (ve 300ms tıklama gecikmesini) kaldırır.
   Parmakla yakınlaştırma ve kaydırma KORUNUR — `maximum-scale=1`in aksine erişilebilirlik
   kaybı yok. Bilinçli olarak yalnız `re-app` kabuğunda: docs sitesi normal bir sayfa,
   orada çift dokunmayla yakınlaştırma meşru. */
/* ⚠ Bu kuralın YARISI. `.re-app` yalnız kabuğun içindeki çift dokunmayı kesiyor; sayfanın
   KENDİ çift dokunma yakınlaştırmasını kesmek `html` ve `body`nin işi ve o host'a ait —
   `overscroll-behavior`da olduğu gibi (re-main'in yorumu: DS'in kuralı belgenin sıçramasını
   durdurmaz, host'un kuralı durdurur). Host tarafı, tüketicinin stylesheet'inde:
       html, body { touch-action: manipulation }
   ⚠ Bunu `<meta viewport maximum-scale=1>` ile "düzeltmek" yerine geçmez ve daha kötüsüdür:
   odak yakınlaştırmasını keserken parmakla yakınlaştırmayı da öldürür. Bir denetim
   yazılacaksa ikisini birden aramalı — `html` VE `body`de `touch-action`, artı viewport'ta
   `maximum-scale`/`user-scalable=no` reddi — yoksa ikinci yolla "düzeltilmiş" bir proje
   sessizce geçer. (labs §N9-1: yedi tüketicinin yedisi bugün doğru yazıyor, yani kırıklık
   gitti; belirsizlik gitmedi — bu satır onun için.) */
.re-app{touch-action:manipulation}

@media (pointer: coarse){

  /* V6-6 — iOS, yazı boyutu 16px'in ALTINDA olan bir form alanına odaklanınca sayfayı
     otomatik yakınlaştırıyor; düzen kayıyor ve dokunmak istediğin hedef yerinden oynuyor.
     DS'in girdileri 12–15px arasında, yani `--font-scale` en yüksek yoğunlukta (1.06) bile
     HİÇBİRİ eşiğe ulaşmıyor:
        re-input 13px · -sm 12px · -lg 14px · re-textarea 13px · re-select 13/12/14px
        re-search-input 13px (sabit) · re-prompt-input 15px · -bar-sm 14px
     `re-money-input input` (24px) eşiğin üstünde, o yüzden burada yok.
     ⚠ İnen biçimler (`-sm`/`-lg`) AYRICA yazılmak zorunda: onların özgüllüğü 0,2,0,
     tek sınıflık bir kural (0,1,0) onlara yetişmez ve `-sm`e geçilen gün sessizce kaybeder.
     ⚠ `<meta viewport maximum-scale=1>` yolu seçilmedi: odak yakınlaştırmasını keserken
     parmakla yakınlaştırmayı da öldürüyor. */
  .re-input,.re-input-sm,.re-input-lg,
  .re-textarea,
  .re-select,.re-select-sm,.re-select-lg,
  .re-search-input,
  .re-brand-menu-search .re-search-input,   /* 0,2,0 — the brand menu sizes it to the names; touch still needs 16 */
  .re-tag-input-field,
  .re-prompt-input,
  .re-prompt-bar-sm .re-prompt-input,
  .re-prompt-bar-lg .re-prompt-input{font-size:16px}

  /* V6-5 — dokunma hedefi tabanı. Apple 44×44pt, Material 48dp önerir; DS'in kompakt
     dilinde GÖRSEL boyutlar doğru duruyor (compact yoğunlukta ikon düğmesi 26px), sorun
     dokunulabilir ALANIN aynı 26–32px olması.
     Çözüm: görünmez bir `::before` katmanı hedefi büyütüyor — düzeni HİÇ etkilemiyor,
     görsel yükseklik değişmiyor. `max(100%,44px)` yoğunluktan bağımsız: zaten 44px'i
     geçen bir öğede hiçbir şey değişmez (metinli düğmeler, geniş sekmeler).
     ⚠ `re-btn-text` HARİÇ — onun `::after`ı zaten alttan çizilen çizgi (satır 58).
     ⚠ Katman `::before`da: `::after` `re-tooltip`in balonu (§N9-4).
     ⚠ Yatay genişleme yalnız kare hedeflerde: çip/sekme için `width:100%`, yoksa satır
     komşusunun alanına biner. Dikey genişleme sarılan (`flex-wrap`) çip grubunda
     compact yoğunlukta gap'i (6px) 2px aşar — o 2px'te DOM'da SONRAKİ öğe kazanır. */
  /* v7 — button/a.re-person joined this list when the person PICKER made re-person an
     interactive control: 26px tall at compact density. Same finding as V6-5 on the
     neighbouring axis — the touch layer covered the controls that existed when it was
     written, and a later component walked straight past it. Only the interactive forms;
     a plain span.re-person is a label and needs no target. */
  .re-btn:not(.re-btn-text),.re-chip,.re-page-tab,.re-tab,.re-modal-close,.re-segment-item,
  button.re-person,a.re-person,button.re-badge,a.re-badge,.re-tag-remove,.re-board-add,
  .re-road-bar,.re-road-milestone,.re-note-pin,.re-cal-event,.re-cal-chip{position:relative}
  /* ::before, not ::after — the tap target and re-tooltip both wanted ::after and the tap
     target won it: `.re-btn:not(.re-btn-text)::after` is (0,2,1) against `.re-tooltip::after`
     at (0,1,1), so on a touch pointer a tooltip on a BUTTON had its content replaced by "".
     Measured by injecting this rule without its media query: a `span.re-tooltip` kept
     `"Kaldır"`, a `button.re-btn.re-tooltip` came back `""` at 44x44.
     ⚠ Today's VISIBLE cost is zero and the report overstates it: re-tooltip only reveals on
     :hover/:focus-visible, neither of which a touch pointer has, so the bubble was invisible
     there either way — labs v11 already answered that with documentation, not CSS. What was
     actually broken is that two unrelated jobs owned one pseudo-element, so neither could
     grow. ::before is free on all of these (checked) and the tap layer has no content to
     order, so it is the one that moves. (labs §N9-4) */
  .re-btn:not(.re-btn-text)::before,.re-modal-close::before,.re-board-add::before{content:"";position:absolute;
    left:50%;top:50%;transform:translate(-50%,-50%);width:max(100%,44px);height:max(100%,44px)}
  .re-chip::before,.re-page-tab::before,.re-tab::before,.re-segment-item::before,
  button.re-person::before,a.re-person::before,
  button.re-badge::before,a.re-badge::before,
  .re-tag-remove::before,.re-road-bar::before,.re-road-milestone::before,
  /* ⚠ re-cal-chip STAYS on ::after and that is not an oversight: its ::before is a VISIBLE
     5px colour dot (:3754), so moving the tap layer there would have replaced the dot with an
     invisible 44x44 box on touch — the calendar's colour coding gone, on phones only.
     Every other host in this block was checked for a ::before of its own and has none.
     ⚠ `pnpm check:css` cannot see this class of clash: it compares a selector declared twice
     in the SAME context, and a @media block is a different one. Found by grepping each host
     for `::before` before trusting the move, not by the gate. */
  .re-note-pin::before,.re-cal-event::before,.re-cal-chip::after{content:"";position:absolute;
    left:0;top:50%;transform:translateY(-50%);width:100%;height:max(100%,44px)}
}


/* ══ re-lp — a product's landing page (LP1, 2026-10-07) ═══════════════════════════════════════════════════════════════
   The page a signed-out visitor gets at the product's own address (plan.redev.co/ → landing; signed in → the app). One template
   for every product, so the family reads as one: the product's colour comes from <html data-brand>, nothing else changes.
   Static HTML + these classes; no script is needed to render it (B-11: mobile Lighthouse ≥ 90).
   Rhythm: every block is a re-lp-section with the same vertical padding; the column is container-lg; text runs at most 38rem
   wide so a line stays readable on a 1440px screen. */
.re-lp{--re-lp-pad:clamp(56px, 9vw, 112px);--re-lp-measure:38rem;background:var(--background);color:var(--foreground)}
.re-lp-nav{position:sticky;top:0;z-index:20;background:color-mix(in oklab,var(--background) 86%,transparent);
  -webkit-backdrop-filter:saturate(1.4) blur(12px);backdrop-filter:saturate(1.4) blur(12px);border-bottom:1px solid color-mix(in oklab,var(--border) 60%,transparent)}
.re-lp-nav > .container{display:flex;align-items:center;gap:var(--re-space-3);min-height:60px}   /* the bar's content on the page's column: brand x = title x */
.re-lp-nav-brand{display:inline-flex;align-items:center;gap:10px;font-weight:700;font-size:calc(1rem * var(--font-scale));color:var(--foreground);text-decoration:none}
.re-lp-nav-end{margin-left:auto;display:flex;align-items:center;gap:var(--re-space-2)}
/* Page links beside the brand, on the left (Ersin 2026-10-08: "apps gibi about da … logo tarafına align"); the right holds icon buttons
   and Sign in. On a phone the links step aside: the hero's own button reaches the apps. */
.re-lp-nav-links{display:flex;align-items:center;gap:2px;margin-left:var(--re-space-3)}
.re-lp-nav-links a{padding:6px 10px;border-radius:var(--radius-control);color:var(--muted-foreground);font-size:calc(0.875rem * var(--font-scale));
  font-weight:500;text-decoration:none;transition:color .15s,background .15s}
.re-lp-nav-links a:hover{color:var(--foreground);background:color-mix(in oklch,var(--foreground) 6%,transparent)}
@media (max-width:640px){.re-lp-nav-links{display:none}}
/* An in-page link lands its section below the sticky bar with a breath above the title (Ersin: "başlık menünün altında kalıyor").
   61px = the bar (60 + its rule). */
.re-lp [id]{scroll-margin-top:calc(61px + clamp(24px, 4vw, 36px))}
/* The last section has nothing below it to scroll into, so its link stopped short and the band above showed (Ersin: "About'a
   tıklayınca üst section'ın arka planı görünüyor"). Only while it is the target, it is one screen tall (less the bar); untargeted,
   the page keeps its own length. */
.re-lp main > section:last-of-type:is(:target, :has(:target)){min-height:calc(100svh - 61px)}
.re-lp-section{padding-block:var(--re-lp-pad)}
.re-lp-section + .re-lp-section{padding-top:0}
.re-lp-eyebrow{display:inline-flex;align-items:center;gap:8px;margin:0 0 14px;font-size:calc(0.8125rem * var(--font-scale));font-weight:600;color:var(--brand)}
/* Measures in em, not ch: ch is the width of the font's "0", which differs between Inter and its fallback, so a ch measure
   changed the column width when Inter swapped in and re-broke every line (landing CLS, 2026-10-07). Each value is the old
   ch measure converted in Inter at that rule's weight (title 18ch, h2 22ch, cta h2 24ch, statement 34ch). */
.re-lp-title{margin:0;max-width:12.13em;font-size:calc(var(--font-scale) * clamp(2.1rem, 6vw, 3.6rem));font-weight:750;line-height:1.05;letter-spacing:-.025em;text-wrap:balance}
.re-lp-h2{margin:0;max-width:14.83em;font-size:calc(var(--font-scale) * clamp(1.6rem, 4vw, 2.4rem));font-weight:700;line-height:1.12;letter-spacing:-.02em;text-wrap:balance}
.re-lp-lead{margin:18px 0 0;max-width:var(--re-lp-measure);font-size:calc(var(--font-scale) * clamp(1.0625rem, 2vw, 1.25rem));line-height:1.55;color:var(--muted-foreground);text-wrap:pretty}
.re-lp-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--re-space-3);margin-top:28px}
.re-lp-hero{padding-top:clamp(40px, 7vw, 88px)}
.re-lp-hero .re-lp-shot{margin-top:clamp(40px, 6vw, 64px)}
/* The screenshot frame: the product's own screen, on a soft brand-tinted stage. The <img> carries width and height (no layout
   shift); the frame keeps the shot's ratio and never crops it. */
.re-lp-shot{position:relative;margin:0;padding:clamp(10px, 2vw, 18px);border-radius:calc(var(--radius-surface) + 6px);
  background:linear-gradient(160deg,color-mix(in oklab,var(--brand) 16%,var(--background)),color-mix(in oklab,var(--brand) 4%,var(--background)))}
.re-lp-shot > img,.re-lp-shot > picture > img{display:block;width:100%;height:auto;border-radius:var(--radius-surface);border:1px solid var(--border-surface);
  box-shadow:0 18px 50px -24px color-mix(in oklab,var(--brand) 45%,transparent);background:var(--card)}
.re-lp-shot > figcaption{margin-top:10px;text-align:center;font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
/* Feature blocks: three across, two at tablet, one on a phone. The icon well is the brand's tint, the same well as re-tile-icon. */
.re-lp-features{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(20px, 3vw, 32px);margin-top:clamp(28px, 4vw, 44px)}
@media (max-width:900px){.re-lp-features{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.re-lp-features{grid-template-columns:minmax(0,1fr)}}
.re-lp-feature{display:flex;flex-direction:column;gap:8px;min-width:0}
.re-lp-feature-icon{font-size:1rem;--re-icon-ratio:1.25;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;margin-bottom:6px;border-radius:var(--radius-md);
  background:color-mix(in oklab,var(--brand) 14%,transparent);color:var(--brand)}
.re-lp-feature-icon svg{width:var(--re-icon);height:var(--re-icon)}   /* 20px: 1rem × 1.25, declared on the well (as re-tile-icon) */
.re-lp-feature h3{margin:0;font-size:calc(1.0625rem * var(--font-scale));font-weight:650}
.re-lp-feature p{margin:0;font-size:calc(0.9375rem * var(--font-scale));line-height:1.55;color:var(--muted-foreground)}
/* A deep dive: text and a shot side by side, alternating; stacked under 860px (text first). */
.re-lp-split{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);align-items:center;gap:clamp(28px, 5vw, 64px)}
.re-lp-split-reverse{grid-template-columns:minmax(0,7fr) minmax(0,5fr)}
.re-lp-split-reverse > :first-child{order:2}
@media (max-width:860px){.re-lp-split,.re-lp-split-reverse{grid-template-columns:minmax(0,1fr)}.re-lp-split-reverse > :first-child{order:0}}
/* FAQ: the DS accordion as is (S24-8), under a heading; readable width. */
.re-lp-faq{max-width:46rem;margin-top:clamp(24px, 4vw, 36px)}
/* The closing call: one band in the brand's tint, one sentence, one button. */
.re-lp-cta{display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:clamp(36px, 6vw, 64px) clamp(20px, 4vw, 48px);
  border-radius:calc(var(--radius-surface) + 6px);background:color-mix(in oklab,var(--brand) 10%,var(--card));border:1px solid color-mix(in oklab,var(--brand) 22%,transparent)}
.re-lp-cta .re-lp-h2{max-width:16.18em}
.re-lp-cta .re-lp-lead{margin-top:0}
.re-lp-cta .re-lp-lead,.re-lp-cta .re-lp-note{color:color-mix(in oklab,var(--foreground) 78%,transparent)}   /* muted grey on the brand tint read 4.23:1 (Lighthouse); the foreground mix keeps ≥ 4.5 */
.re-lp-foot{padding-block:28px 36px;border-top:1px solid var(--border);background:var(--background);font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-lp-foot-row{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:12px 24px}   /* baseline: re-btn-text's 3px underline pad lifted the right side 2px over the left */
.re-lp-foot a{color:inherit;text-decoration:none}
/* Footer links are text buttons (re-btn-text: brand colour and a rule that draws in on hover; Ersin 2026-10-07). This hover
   stays for a plain <a> a product has not migrated yet. */
.re-lp-foot a:not(.re-btn-text):hover{color:var(--foreground);text-decoration:underline;text-underline-offset:2px}
.re-lp-foot nav{display:flex;flex-wrap:wrap;gap:8px 18px}
/* The colophon on the left speaks the footer's type, not the app colophon's 12px with its always-on underline: both sides of the
   bar are one line of fine print (Ersin 2026-10-07, after the maker became a link there). */
.re-lp-foot .re-colophon{margin:0;font-size:inherit}
.re-lp-foot .re-colophon a{border-bottom:0}
/* Centred colophon, no strip (redev.me, Ersin 2026-10-08: "footer'ı şerit olmaktan çıkar, center align, eskisi gibi"): when the page
   ends on a band, a ruled bar under the band's own rule closed the page twice. Lines stack in the middle (©, the legal links, the
   motto), and no ground of its own: the dotted ambient runs under it (Ersin: "body background'unu göremiyoruz"; the white was
   for the ruled bar). The product landings keep the bar. */
.re-lp-foot.re-lp-foot-center{border-top:0;background:transparent;padding-block:clamp(36px, 5vw, 56px) clamp(40px, 6vw, 64px);text-align:center}
.re-lp-foot-center .re-lp-foot-row{flex-direction:column;align-items:center;justify-content:center;gap:8px}
/* © first, the legal links, the motto last — from the same markup as the bar (colophon: © · motto; nav: links). */
.re-lp-foot-center .re-colophon{display:contents}
.re-lp-foot-center .re-colophon > span + span::before{content:none}
.re-lp-foot-center .re-colophon > span:first-child{order:1}
.re-lp-foot-center .re-lp-foot-row > nav{order:2}
.re-lp-foot-center .re-colophon > span:last-child:not(:first-child){order:3}
/* Invite-only products (plan, split: Ersin 2026-10-07): the call never promises an open sign-up. The primary button is Sign in;
   this note under it tells the invited how to get in. */
.re-lp-note{margin:12px 0 0;font-size:calc(0.875rem * var(--font-scale));color:var(--muted-foreground)}
.re-lp-note strong{color:var(--foreground);font-weight:600}
/* The problem, in the visitor's words: one large paragraph, no box. */
.re-lp-statement{margin:0;max-width:21.94em;font-size:calc(var(--font-scale) * clamp(1.35rem, 3.2vw, 1.9rem));font-weight:500;line-height:1.35;
  letter-spacing:-.01em;color:var(--muted-foreground);text-wrap:pretty}
.re-lp-statement strong{color:var(--foreground);font-weight:650}
/* A phone screenshot: the frame narrows to a phone and centres; the shot keeps its tall ratio. */
.re-lp-shot-phone{width:100%;max-width:280px;margin-inline:auto;padding:12px;border-radius:44px}
.re-lp-shot-phone > img,.re-lp-shot-phone > picture > img{border-radius:32px}
/* Steps: an ordered list whose numbers sit in the brand's well. Three, at most four. Each step is a box (Ersin 2026-10-07, on
   split's "Nasıl başlanır": the three loose columns did not read as one sequence) — the product card's box without its hover,
   because a step is not a link; the gap is the product cards' too, so both card rows on a page share one rhythm. */
.re-lp-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px, 2vw, 20px);margin:clamp(28px, 4vw, 44px) 0 0;padding:0;
  list-style:none;counter-reset:re-lp-step}
@media (max-width:760px){.re-lp-steps{grid-template-columns:minmax(0,1fr)}}
.re-lp-steps > li{counter-increment:re-lp-step;display:flex;flex-direction:column;gap:8px;min-width:0;padding:clamp(18px, 2.4vw, 24px);
  border:1px solid var(--border-surface);border-radius:var(--radius-surface);background:var(--card)}
.re-lp-steps > li::before{content:counter(re-lp-step);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;
  margin-bottom:4px;border-radius:50%;background:color-mix(in oklab,var(--brand) 14%,transparent);color:var(--brand);font-weight:700;font-size:calc(0.875rem * var(--font-scale))}
.re-lp-steps h3{margin:0;font-size:calc(1.0625rem * var(--font-scale));font-weight:650}
.re-lp-steps p{margin:0;font-size:calc(0.9375rem * var(--font-scale));line-height:1.55;color:var(--muted-foreground)}
/* Product cards: the family page lists its products, and a product page points to a sibling. A card is ONE link (the whole card),
   its own colour from data-brand on the card, "Learn more" as the visible affordance. */
.re-lp-products{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr));gap:clamp(14px, 2vw, 20px);margin-top:clamp(24px, 4vw, 36px)}
.re-lp-products:has(> :nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}   /* four cards: 2 × 2, not 3 + an orphan */
@media (max-width:640px){.re-lp-products:has(> :nth-child(4):last-child){grid-template-columns:minmax(0,1fr)}}
/* Few products (redev.me, Ersin 2026-10-08): one or two cards keep the width of a column of three and sit in the middle; stretched
   to halves they read as a different, emptier layout. From 900px, where the row has three columns; below it the grid's own. */
@media (min-width:900px){.re-lp-products:not(:has(> :nth-child(3))){grid-template-columns:repeat(auto-fit,calc((100% - 2 * clamp(14px, 2vw, 20px)) / 3));justify-content:center}}
/* Studio page (redev.me, Ersin 2026-10-07: keep the live site's identity — centred hero, the platform, the manifesto, the dotted
   ground). re-lp-center centres a section: its heading, lead and actions. */
.re-lp-center{text-align:center}
.re-lp-center :is(.re-lp-title,.re-lp-h2,.re-lp-lead,.re-lp-statement){margin-inline:auto}
.re-lp-center .re-lp-actions{justify-content:center}
.re-lp-center .re-lp-eyebrow{justify-content:center}
/* What sits inside a centred section reads left: the cards. Only the section's own head and statement centre. */
.re-lp-center .re-lp-products{text-align:left}
/* A centred section's title is the small label (the live site's OUR PLATFORM · ABOUT); as an h2 it keeps the outline. */
h2.re-lp-eyebrow{display:flex;margin-bottom:22px}
/* The brand word in a heading (Re-<build>, a <philosophy>). */
.re-lp-accent{color:var(--brand)}
/* Ambient ground: the DS's re-dots + re-glow held still behind the whole page, first child of <body>. Everything after it is
   lifted above it; the bar keeps its own sticky stacking. CSS only: the page's interactive canvas, if any, is its own. */
.re-lp-ambient{position:fixed;inset:0;z-index:0;pointer-events:none}
.re-lp-ambient ~ :not(.re-lp-nav){position:relative;z-index:1}
/* On the dotted ground a band is frosted glass, not a grey slab (Ersin 2026-10-08: "çok gri kalıyor … buzlu cam … noktalar ve
   background akışı görünsün … alt üst border … navbardakinden de hafif"): a see-through white with a light blur, so the dots and
   the glow show through softly, and a hairline above and below, fainter than the bar's (60%). Without the ground (a product
   landing) the band keeps its plain muted fill. */
.re-lp-ambient ~ main .re-lp-band{background:color-mix(in oklab,var(--background) 50%,transparent);
  -webkit-backdrop-filter:blur(1px) saturate(1.2);backdrop-filter:blur(1px) saturate(1.2);
  border-block:1px solid color-mix(in oklab,var(--border) 40%,transparent)}
/* Manifesto: a centred card with the studio's belief — a heading with an accent word, then short lines. The statement's card form. */
.re-lp-manifesto{position:relative;overflow:hidden;padding:clamp(36px, 6vw, 64px) clamp(20px, 4vw, 48px);border:1px solid var(--border-surface);
  border-radius:var(--radius-surface);background:var(--card);text-align:center}
.re-lp-manifesto :is(h2,h3){margin:0 auto;max-width:22em;font-size:calc(var(--font-scale) * clamp(1.6rem, 4vw, 2.4rem));font-weight:750;line-height:1.15;
  letter-spacing:-.02em;text-wrap:balance}
.re-lp-manifesto p{margin:12px auto 0;max-width:var(--re-lp-measure);font-size:calc(var(--font-scale) * clamp(1.0625rem, 2vw, 1.2rem));line-height:1.6;
  color:var(--muted-foreground)}
.re-lp-manifesto :is(h2,h3) + p{margin-top:24px}
/* Whatever stands above the manifesto (a chip, a note) keeps a breath from it; they touched (redev.me). */
* + .re-lp-manifesto{margin-top:clamp(24px, 4vw, 36px)}
/* re-notify — the family's "tell me when" list (redev.me, Ersin 2026-10-08: "beta tester olmak isterseniz listemize kaydolun"),
   a module of its own (docs /docs/pattern-notify), not only the landing's. One email field and one button on a row (stacked under
   480px), a promise line, and a status line the page's script fills (data-state="ok" | "error"). The DS draws it; where the
   address goes is the page's. The status text mixes toward the foreground: plain --success / --danger read 1.96 / 4.00 on white. */
.re-notify{max-width:28rem;margin:28px 0 0;text-align:left}
.re-notify-row{display:flex;gap:8px}
.re-notify-row .re-input{flex:1;min-width:0}
.re-notify-row .re-btn{flex-shrink:0}
.re-notify-hint{margin:10px 0 0;font-size:calc(0.8125rem * var(--font-scale));color:var(--muted-foreground)}
.re-notify-hint{text-wrap:balance}
.re-notify-hint a{color:inherit;text-underline-offset:2px;white-space:nowrap}   /* the link never wraps alone onto its own line */
/* A challenge widget (Turnstile) slot between the row and the hint: room only when the widget is there. */
.re-notify-challenge:not(:empty){margin-top:10px}
.re-lp-center .re-notify-challenge{display:flex;justify-content:center}
.re-notify-msg{margin:10px 0 0;min-height:1.4em;font-size:calc(0.875rem * var(--font-scale));font-weight:500}
.re-notify-msg[data-state="ok"]{color:color-mix(in oklch,var(--success) 45%,var(--foreground))}
.re-notify-msg[data-state="error"]{color:color-mix(in oklch,var(--danger) 60%,var(--foreground))}
/* In a centred section (a landing) the form sits in the middle and its two lines centre under it. */
.re-lp-center .re-notify{margin-inline:auto}
.re-lp-center :is(.re-notify-hint,.re-notify-msg){text-align:center}
/* Stacked, the field keeps its own height: flex:1 in a column is a 0 basis, and it collapsed to its text (390, measured). */
@media (max-width:480px){.re-notify-row{flex-direction:column}.re-notify-row .re-input{flex:none}.re-notify-row .re-btn{width:100%}}
/* A chip: one short fact in a white pill, readable on the dotted ground (Ersin: "chip/badge gibi beyaz zeminli hap"). */
/* 16px radius, not full: one line it is still a pill (30px tall), and when a phone breaks it onto two lines it is a rounded box, not
   a stretched capsule (390, measured). Balanced lines. */
.re-lp-chip{display:inline-flex;align-items:center;margin:0;padding:6px 14px;border:1px solid var(--border);border-radius:16px;text-wrap:balance;
  background:var(--card);color:var(--muted-foreground);font-size:calc(0.875rem * var(--font-scale));line-height:1.3}
/* A card that is not a link (a platform layer) keeps the box without the hover: the hover rule is a:hover only. */
/* A status on the card (Beta) sits in the head, at the end: in the sentence it read as part of the description. */
.re-lp-product-head > .re-badge{margin-left:auto;font-weight:600}
.re-lp-product{display:flex;flex-direction:column;gap:10px;min-width:0;padding:clamp(18px, 2.4vw, 24px);border:1px solid var(--border-surface);
  border-radius:var(--radius-surface);background:var(--card);color:var(--foreground);text-decoration:none;transition:border-color .15s,transform .15s}
.re-lp-product:is(a):hover{border-color:color-mix(in oklab,var(--brand) 45%,var(--border-surface))}
.re-lp-product-head{display:flex;align-items:center;gap:10px;font-weight:700;font-size:calc(1.0625rem * var(--font-scale))}
.re-lp-product p{margin:0;flex:1;font-size:calc(0.9375rem * var(--font-scale));line-height:1.5;color:var(--muted-foreground)}
.re-lp-product-more{display:inline-flex;align-items:center;gap:6px;font-size:calc(0.875rem * var(--font-scale));font-weight:600;color:var(--brand)}
.re-lp-product-more svg{width:var(--re-icon);height:var(--re-icon);transition:transform .15s}
.re-lp-product:hover .re-lp-product-more svg{transform:translateX(3px)}
@media (prefers-reduced-motion:reduce){.re-lp-product,.re-lp-product-more svg{transition:none}}
/* A long read on the landing kit (privacy, terms, a story): re-prose at reading size. The page title and its date are the
   head; the text runs at most 42rem; a heading takes more room above than below, so it reads as the start of what follows. */
/* A long read (privacy, terms) sits as one 42rem column in the MIDDLE of the page, text left-aligned (Ersin 2026-10-08: "container
   daralıyor; center align"), at a type size between the live Jekyll page (13px) and the first kit (17px): 15px body, 20px h2,
   40px h1. */
.re-lp-doc-head{display:flex;flex-direction:column;gap:10px;max-width:42rem;margin:0 auto clamp(28px, 4vw, 40px)}
.re-lp-doc-head .re-lp-title{max-width:none;font-size:calc(var(--font-scale) * clamp(1.9rem, 5vw, 2.5rem))}
.re-lp-doc-meta{margin:0;font-size:calc(0.875rem * var(--font-scale));color:var(--muted-foreground)}
.re-prose.re-lp-prose{--re-prose-size:0.9375rem;max-width:42rem;margin-inline:auto;line-height:1.65}
.re-lp-prose > * + *{margin-top:1em}
.re-lp-prose > * + :is(h2,h3,h4){margin-top:2em}
.re-lp-prose > :is(h2,h3,h4) + *{margin-top:.6em}
.re-lp-prose h2{font-size:1.3333em;letter-spacing:-.01em}
.re-lp-prose h3{font-size:1.15em}
.re-lp-prose :is(ul,ol){gap:.4em}   /* :is, not :where: it has to outweigh .re-prose ul (0,1,1) inside the same layer */
/* A band: a full-width section on a muted ground (Ersin 2026-10-07: "her şey havada gibi duruyor … arada bir iki background
   section nefes aldırır"). Use it to set off a statement and the sibling block; never two in a row. It always takes the full top
   padding, and so does the section after it (the plain sections share one padding between them). */
.re-lp-band{background:color-mix(in oklch,var(--foreground) 3.5%,var(--background))}
.re-lp-section.re-lp-band,.re-lp-band + .re-lp-section{padding-top:var(--re-lp-pad)}
.re-lp-split .re-lp-products{grid-template-columns:minmax(0,1fr);margin-top:0}   /* a sibling card beside its text fills the column */
.re-lp-band :is(.re-lp-lead,.re-lp-statement){color:color-mix(in oklab,var(--foreground) 72%,transparent)}   /* muted grey on the band read 4.47:1 */
/* A sentence on its own line on a wide screen (Ersin: "Will … ve The list ile başlayan cümleler alt satırda olsa"): wrap each sentence
   of a statement or a lead in re-lp-line. On a phone the text flows; forced breaks in a narrow column leave ragged short lines. */
@media (min-width:861px){.re-lp-line{display:block}}
/* FAQ, open: questions and answers in two columns, no box and no toggle (Ersin: the boxed accordion beside its heading "hala
   oturmamış"). Same rhythm as the feature grid and the steps; a visitor reads the answer without a tap. Four to six questions;
   a long list can still use the DS accordion. */
.re-lp-qa{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(24px, 3.5vw, 40px) clamp(24px, 5vw, 64px);margin:clamp(28px, 4vw, 44px) 0 0}
@media (max-width:760px){.re-lp-qa{grid-template-columns:minmax(0,1fr)}}
.re-lp-qa > div{min-width:0}
.re-lp-qa dt{margin:0 0 8px;font-size:calc(1.0625rem * var(--font-scale));font-weight:650;color:var(--foreground)}
.re-lp-qa dd{margin:0;font-size:calc(0.9375rem * var(--font-scale));line-height:1.6;color:var(--muted-foreground)}

/* ══ The product shown moving (LP2, Ersin 2026-10-07: "sayfa scroll olurken ekran içinde görselin de scroll olduğu ya da galeri gibi
   wizard görünüm için ekran değiştiği bir senaryo … DS tanımlı olsun, diğer ürünlerde de kullanırız") ════════════════════════
   re-lp-screen is the phone's screen: a fixed 9:19.5 window inside re-lp-shot-phone. Two behaviours sit on it:
     re-lp-screen-scroll  one tall screenshot that scrolls INSIDE the screen while the page scrolls past (CSS scroll-driven
                          animation, no script). Where unsupported, or with reduced motion, the top of the shot stays still.
     re-lp-story          steps on one side, the phone sticky on the other; the screen shows the step in view (reLpStory, a few
                          lines of DS script). On a phone there is no sticky phone: each step carries its own screen inline. */
.re-lp-screen{position:relative;aspect-ratio:9/19.5;overflow:hidden;border-radius:32px;border:1px solid var(--border-surface);background:var(--card);
  box-shadow:0 18px 50px -24px color-mix(in oklab,var(--brand) 45%,transparent);container-type:size}
.re-lp-screen > img{position:absolute;inset:0 0 auto 0;display:block;width:100%;height:auto;border:0;border-radius:0;box-shadow:none}
.re-lp-shot-phone > .re-lp-screen{margin:0}   /* the frame needs width:100% (above): a screen has size containment, so a shrink-to-fit frame would collapse to its borders (measured: 4px) */
/* The timeline is the SCREEN's passage through the viewport, not the image's: the image is taller than the screen, so its own
   view progress would run on the wrong scale. From the screen fully in view to the screen leaving, the shot runs top to bottom. */
@supports (animation-timeline: view()){
  .re-lp-screen-scroll{view-timeline-name:--re-lp-screen}
  .re-lp-screen-scroll > img{animation:re-lp-screen-scroll linear both;animation-timeline:--re-lp-screen;animation-range:contain 0% exit 60%}
  /* In the hero the screen is in view at load, so its own timeline starts part-way (measured: the shot opened 125px down, the title
     hidden). There the page's scroll drives it: top of the page = top of the shot, the end of the shot by 80vh. */
  .re-lp-hero .re-lp-screen-scroll > img{animation-timeline:scroll(root);animation-range:0 80vh}
}
@keyframes re-lp-screen-scroll{from{transform:translateY(0)}to{transform:translateY(calc(-100% + 100cqh))}}
/* Story: steps beside a sticky phone whose screen follows the step in view. */
.re-lp-story{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,5fr);gap:clamp(28px, 5vw, 72px);align-items:start}
.re-lp-story-steps{margin:0;padding:0;list-style:none;counter-reset:re-lp-step}
.re-lp-story-steps > li{counter-increment:re-lp-step;display:flex;flex-direction:column;justify-content:center;gap:10px;min-height:min(80vh,640px);
  padding-block:clamp(24px, 4vw, 40px);opacity:.45;transition:opacity .3s}
.re-lp-story-steps > li.on,.re-lp-story:not([data-re-story-ready]) .re-lp-story-steps > li{opacity:1}
.re-lp-story-steps > li::before{content:counter(re-lp-step);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;
  border-radius:50%;background:color-mix(in oklab,var(--brand) 14%,transparent);color:var(--brand);font-weight:700;font-size:calc(0.875rem * var(--font-scale))}
.re-lp-story-steps h3{margin:0;font-size:calc(var(--font-scale) * clamp(1.35rem, 2.6vw, 1.75rem));font-weight:700;letter-spacing:-.015em;line-height:1.2}
.re-lp-story-steps p{margin:0;max-width:32rem;font-size:calc(var(--font-scale) * 1.0625rem);line-height:1.6;color:var(--muted-foreground)}
.re-lp-story-phone{position:sticky;top:calc(50vh - min(42vh, 330px));align-self:start}
.re-lp-story-phone .re-lp-screen > img{opacity:0;transition:opacity .35s}
.re-lp-story-phone .re-lp-screen > img.on,.re-lp-story:not([data-re-story-ready]) .re-lp-story-phone .re-lp-screen > img:first-child{opacity:1}
.re-lp-story-inline{display:none}
@media (max-width:860px){
  .re-lp-story{grid-template-columns:minmax(0,1fr)}
  .re-lp-story-phone{display:none}
  .re-lp-story-steps > li{min-height:0;opacity:1}
  .re-lp-story-inline{display:block;max-width:260px;margin:14px auto 0}
}
@media (prefers-reduced-motion:reduce){
  .re-lp-screen-scroll > img{animation:none}
  .re-lp-story-steps > li,.re-lp-story-phone .re-lp-screen > img{transition:none}
}

}  /* @layer components — the landing kit is inside it too: written after this brace at first, re-lp rules were unlayered
     and beat every layered rule whatever its specificity (a re-btn-text in the footer lost its hover colour; no utility could
     override a re-lp property). Ersin 2026-10-07. */
