/* Home grid: icons + widgets in one auto-flow layout.
   Used only by the home page's icon view.

   Layout
   ──────
   - Cells ≈ --cell × --row (96 × 116). Icon 48 px + ~56 px label
     keeps the cell close to square; 3×2 widget reads as a landscape
     rectangle (~312 × 244).
   - auto-fill packs as many columns as fit at min --cell each. On
     phones (≤480 px) we floor at 4 fixed columns; data-heavy widgets
     override to `grid-column: 1 / -1` there because a 2×2 widget at
     ~80 px cell width can't render its bars + credits legibly. The
     lightweight text-box widget keeps its selected 1–2 column span.
   - `grid-auto-flow: row dense` lets the layout backfill around the
     taller widget tiles.
   - Row-delete "−" buttons sit INSIDE the grid (overlapping the
     leftmost placeholder of an empty row); no reserved gutter, so
     the grid's left edge doesn't jump when entering edit mode.
*/
.home-grid {
  --cell: 96px;
  --row: 116px;
  --gap: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--cell), 1fr));
  grid-auto-rows: var(--row);
  grid-auto-flow: row dense;
  gap: var(--gap);
  margin-bottom: 16px;
  position: relative;
}
@media (max-width: 480px) {
  .home-grid {
    grid-template-columns: repeat(4, 1fr);
    --cell: calc((100vw - 32px - 3 * var(--gap)) / 4);
  }
  .home-grid .widget-tile:not(.widget-text-box) {
    grid-column: 1 / -1 !important;
  }
  /* Pinned tiles store desktop grid coordinates inline. On phones those
     coordinates can point past the 4-column layout, which makes CSS Grid
     create extra implicit columns and crush the visible page. Keep the pin
     marker, but let mobile flow naturally. */
  .home-grid > [data-pin] {
    grid-column: auto / span 1 !important;
    grid-row: auto / span 1 !important;
  }
  .home-grid > .widget-tile[data-pin]:not(.widget-text-box) {
    grid-column: 1 / -1 !important;
  }
  .home-grid > .widget-tile[data-h="2"] {
    grid-row: auto / span 2 !important;
  }
  .home-grid > .widget-tile[data-h="3"] {
    grid-row: auto / span 3 !important;
  }
  .home-grid > .widget-text-box[data-w="2"] {
    grid-column: auto / span 2 !important;
  }
}

/* ── Tile inner box ─────────────────────────────────────────
   Jiggle animation is applied here (not on the outer tile) so the
   outer tile can freely take a FLIP `transform` during reorder. */
.home-grid .tile-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  padding: 6px 4px 4px;
  height: 100%;
  box-sizing: border-box;
}
.home-grid .widget-inner {
  align-items: stretch;       /* widgets fill their cell */
  padding: 10px 12px;
  gap: 6px;
}

/* Home-grid icons are smaller than the categories tile (48 vs 56) so
   each cell gets ~56 px of label height ≈ 3 lines of 12-px CJK at
   line-height 1.3. */
.home-grid .el-icon-tile {
  width: 48px;
  min-width: 48px;
  max-width: 48px;
  height: 48px;
  font-size: 24px;
  border-radius: 11px;
}
.home-grid .el-icon-tile.el-icon-emoji { font-size: 34px; }
.home-grid .tile-inner {
  padding: 6px 4px 4px;
  gap: 4px;
}

/* ── Icon tile (replaces .icon-tile from app.css for the home grid) ─
   Same look as the categories grid, but the outer element is
   transform-friendly (used by FLIP). */
.home-grid .icon-tile {
  display: block;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--fg);
  transition: background 0.1s, transform 200ms ease;
  position: relative;
}
.home-grid .icon-tile:hover { background: var(--bg-soft); }
.home-grid .icon-tile-label {
  font-size: 12px;
  text-align: center;
  line-height: 1.3;
  /* overflow-wrap: anywhere breaks long unbroken English strings only
     when they'd overflow — kinder than `word-break: break-all`, which
     splits every word eagerly ("calendar" → "calenda\nr"). CJK still
     breaks naturally per character. */
  word-break: normal;
  overflow-wrap: anywhere;
  width: 100%;
  flex: 1 1 auto;       /* take all remaining vertical space */
  overflow: hidden;     /* clip overflow without forcing line-clamp */
  min-height: 0;
}

/* ── "+ 添加一行" pill ───────────────────────────────────
   Sits just below the grid. The slot is ALWAYS reserved in layout
   so entering edit mode doesn't make the page jump taller — the
   button is just visibility-hidden until .home-grid.editing flips
   it visible. Click adds one full row of empty placeholder cells.
   The right-click "添加占位行" entry remains available outside
   edit mode for users who prefer that path. */
.home-grid-add-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 4px auto 24px;
  width: 56px;
  height: 28px;
  padding: 0;
  border: 1px dashed var(--border);
  border-radius: 14px;
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  opacity: 0.85;
  visibility: hidden;             /* invisible but slot reserved */
  transition: opacity 0.15s ease, color 0.15s ease,
              border-color 0.15s ease, background 0.15s ease, width 0.15s ease;
}
.home-grid.editing + .home-grid-add-btn { visibility: visible; }
.home-grid-add-btn:hover {
  opacity: 1;
  color: var(--fg);
  border-color: var(--fg-muted);
  background: var(--bg-soft);
  width: 72px;
}
.home-grid-add-btn:focus-visible {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
  opacity: 1;
}

/* ── Empty state ────────────────────────────────────────
   Replaces the previous "<section class='empty'>这里还没有元素。"
   with a centered, instructional hint that surfaces all three new
   action paths (right-click / long-press / ⌘K). The .home-grid
   stays mounted (with .is-empty) so right-click on its area still
   pops the blank-area context menu. */
.home-grid-empty {
  grid-column: 1 / -1;
  text-align: center;
  padding: 60px 16px;
  user-select: none;
}
.home-grid-empty p { margin: 4px 0; }
.home-grid-empty kbd {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg);
}
.home-grid.is-empty {
  /* Make the empty grid claim some height so right-click anywhere on
     the page background falls naturally onto it. */
  min-height: 60vh;
}

/* ── Swap-select mode ──────────────────────────────────
   Right-click → "交换位置…" puts the grid here. Same-size siblings stay
   vivid + outlined; the origin gets a dashed outline; everything else
   dims. Non-candidates also lose pointer-events so a click on them
   falls through to document, where context_menu.js cancels the mode. */
.home-grid.swap-selecting > * {
  transition: opacity 120ms ease, filter 120ms ease;
}
.home-grid.swap-selecting > *:not(.swap-candidate):not(.swap-origin) {
  opacity: 0.22;
  filter: grayscale(0.7);
  pointer-events: none;
}
.home-grid.swap-selecting .swap-origin {
  outline: 2px dashed var(--fg-muted);
  outline-offset: 3px;
  border-radius: 10px;
}
.home-grid.swap-selecting .swap-candidate {
  outline: 2px solid #6366f1;
  outline-offset: 3px;
  border-radius: 10px;
  cursor: pointer;
}
.home-grid.swap-selecting .swap-candidate:hover {
  outline-color: #4338ca;
  box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.18);
}

/* ── Row controls (edit mode) ──────────────────────────
   "−" appears on the left of any row that contains no real items
   (only placeholders). Shown by JS on enter-edit and re-evaluated
   after each add/delete/reorder. */
/* Row-delete button sits inside the grid, overlapping the top-left
   corner of the row's leftmost placeholder cell. Same z-index trick
   as widget-controls — placeholder remains draggable around the
   button; button click is e.stopPropagation'd so it doesn't also
   trigger the placeholder-click → exit-edit-mode. */
.row-del-btn {
  position: absolute;
  left: 4px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: #dc2626;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
  z-index: 3;
}
.row-del-btn:hover { background: #fef2f2; border-color: #dc2626; }

/* Icon-tile × in edit mode. Hidden outside .editing. Pointer events
   while editing are kept enabled even though the parent icon-tile is
   touch-action: none, so the click registers; data-no-nav keeps the
   long-press detector off and stopPropagation in the click handler
   prevents the surrounding <a> from navigating. */
.icon-del-btn {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: #dc2626;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.home-grid.editing .icon-del-btn { display: inline-flex; }
.icon-del-btn:hover { background: #fef2f2; border-color: #dc2626; }

/* ── Placeholder cell ────────────────────────────────────
   Empty grid slot the user can drop icons onto without forcing
   everyone else to shift. Invisible (but space-occupying) outside
   edit mode; outlined with a dashed border in edit mode so the user
   can see what's a drop target. */
.grid-placeholder {
  border-radius: var(--radius);
  visibility: hidden;     /* keeps cell occupied without drawing it */
  transition: opacity 0.15s, background 0.15s;
  display: flex;
  align-items: center;
  justify-content: center;
}
.home-grid.editing .grid-placeholder {
  visibility: visible;
  background: rgba(99, 102, 241, 0.04);
  border: 1px dashed rgba(99, 102, 241, 0.35);
}
.placeholder-add-btn {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1.5px dashed rgba(99, 102, 241, 0.55);
  background: transparent;
  color: rgba(99, 102, 241, 0.85);
  cursor: pointer;
  font-size: 22px;
  font-weight: 300;
  line-height: 1;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
}
.home-grid.editing .placeholder-add-btn { display: inline-flex; }
.placeholder-add-btn:hover {
  background: rgba(99, 102, 241, 0.15);
  border-style: solid;
  color: rgba(99, 102, 241, 1);
}

/* ── Widget tile ─────────────────────────────────────────── */
.widget-tile {
  position: relative;
  border-radius: var(--radius-lg);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  cursor: pointer;
  overflow: hidden;
  transition: background 0.1s, transform 200ms ease, box-shadow 0.15s;
}
.widget-tile:hover { background: var(--bg); box-shadow: 0 1px 6px rgba(0,0,0,0.06); }

/* Inner widget styling (small variant of the detail-page widget). */
.widget-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.widget-head strong { font-size: 13px; }
.widget-head .muted { font-size: 11px; }
.widget-head-left {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.widget-head-left strong { white-space: nowrap; }
.widget-head-left .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.widget-icon-btn {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.widget-icon-btn:hover {
  background: var(--bg-soft);
  color: var(--fg);
}
.widget-icon-btn:disabled {
  opacity: 0.55;
  cursor: progress;
}
.widget-icon-btn svg { width: 13px; height: 13px; display: block; }
.widget-icon-btn.spinning svg { animation: widget-refresh-spin 0.8s linear infinite; }
@keyframes widget-refresh-spin { to { transform: rotate(360deg); } }
.widget-foot-actions { display: inline-flex; gap: 4px; flex: 0 0 auto; }
.widget-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  overflow: hidden;
}
.widget-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 2px 8px;
}
.widget-row-label {
  font-size: 11px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px;
  grid-column: 1 / 3;
}
.widget-row-label .xsmall { font-size: 10px; }
.widget-row .usage-bar {
  height: 5px;
  background: #e5e7eb;
  border-radius: 999px;
  overflow: hidden;
  grid-column: 1 / 2;
}
.widget-row .usage-bar span {
  display: block;
  height: 100%;
  background: #2563eb;
  border-radius: 999px;
  transition: width .25s ease;
}
.widget-pct {
  font-size: 10px;
  color: var(--fg-muted);
  white-space: nowrap;
  text-align: right;
  grid-column: 2 / 3;
  min-width: 28px;
}
.widget-foot {
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: auto;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}
.widget-foot .muted { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.widget-extra-row {
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 3px 6px;
  background: rgba(99, 102, 241, 0.06);
  border-radius: 4px;
}
.widget-extra-row .muted { font-size: 10px; }
/* Keep the right-side value on one line — at narrow widget widths
   (2×2 on phones) it otherwise wraps to "0.00 / 7000 / USD"
   stacked vertically. nowrap + ellipsis preserves the row's height. */
.widget-extra-row > :last-child {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.widget-stale {
  font-size: 10px;
  padding: 3px 6px;
  background: #fef3c7;
  color: #92400e;
  border-radius: 4px;
  align-self: flex-start;
}
.widget-empty {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  flex: 1;
  font-size: 12px;
}
.xsmall { font-size: 10px; }

/* ── Text box widget ───────────────────────────────────────
   A title-free Markdown note: reading mode fills the complete tile;
   clicking it swaps the whole surface to a textarea. */
.widget-text-box { cursor: text; }
.widget-text-box .widget-inner {
  padding: 8px 10px;
  gap: 0;
}
.tbw-shell {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0;
  width: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
.tbw-display {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  color: var(--fg);
  cursor: text;
  font-size: 12px;
  line-height: 1.5;
  overflow-wrap: anywhere;
  scrollbar-color: var(--border) transparent;
  scrollbar-width: thin;
  user-select: text;
}
.tbw-display:focus-visible {
  border-radius: 4px;
  outline: 2px solid rgba(37, 99, 235, 0.65);
  outline-offset: -2px;
}
.tbw-display.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-muted);
  font-size: 11px;
  line-height: 1.35;
  text-align: center;
  user-select: none;
}
.tbw-empty-hint { pointer-events: none; }

/* Compact Markdown reading styles. Margins are intentionally tighter
   than the full vault viewer so headings/lists remain useful at 1×1. */
.tbw-display > :first-child { margin-top: 0; }
.tbw-display > :last-child { margin-bottom: 0; }
.tbw-display p { margin: 0 0 0.5em; }
.tbw-display h1,
.tbw-display h2,
.tbw-display h3,
.tbw-display h4,
.tbw-display h5,
.tbw-display h6 {
  margin: 0.65em 0 0.35em;
  line-height: 1.25;
}
.tbw-display h1 { font-size: 1.35em; }
.tbw-display h2 { font-size: 1.2em; }
.tbw-display h3 { font-size: 1.08em; }
.tbw-display h4,
.tbw-display h5,
.tbw-display h6 { font-size: 1em; }
.tbw-display ul,
.tbw-display ol {
  margin: 0.35em 0;
  padding-left: 1.4em;
}
.tbw-display li { margin: 0.12em 0; }
.tbw-display a { color: #2563eb; }
.tbw-display code {
  padding: 0.08em 0.3em;
  border-radius: 3px;
  background: rgba(100, 116, 139, 0.12);
  font: 0.9em/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.tbw-display pre {
  margin: 0.45em 0;
  padding: 0.5em 0.6em;
  overflow: auto;
  border-radius: 5px;
  background: rgba(100, 116, 139, 0.1);
  line-height: 1.4;
}
.tbw-display pre code { padding: 0; background: transparent; }
.tbw-display blockquote {
  margin: 0.4em 0;
  padding-left: 0.65em;
  border-left: 3px solid var(--border);
  color: var(--fg-muted);
}
.tbw-display table {
  border-collapse: collapse;
  margin: 0.4em 0;
  font-size: 0.92em;
}
.tbw-display th,
.tbw-display td {
  padding: 0.2em 0.4em;
  border: 1px solid var(--border);
}
.tbw-display hr {
  margin: 0.65em 0;
  border: 0;
  border-top: 1px solid var(--border);
}
.tbw-display img { max-width: 100%; height: auto; }

.tbw-form {
  display: flex;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  user-select: text;
}
.tbw-input {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  min-height: 0;
  resize: none;
  box-sizing: border-box;
  padding: 2px;
  border: 0;
  border-radius: 4px;
  outline: none;
  background: transparent;
  color: var(--fg);
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.tbw-shell.is-editing {
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.55);
}
.tbw-status {
  position: absolute;
  right: 4px;
  bottom: 3px;
  left: 4px;
  overflow: hidden;
  padding: 2px 4px;
  border-radius: 3px;
  background: var(--bg);
  color: #dc2626;
  font-size: 10px;
  line-height: 1.2;
  pointer-events: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tbw-form[hidden],
.tbw-display[hidden],
.tbw-status[hidden] {
  display: none;
}

/* 1-cell widths need every pixel. */
.widget-text-box[data-w="1"] .widget-inner { padding: 6px; }
.widget-text-box[data-w="1"] .tbw-display { font-size: 11px; line-height: 1.35; }

.home-grid.editing .widget-text-box .tbw-display {
  user-select: none;
}

/* ── 2×1 compact variant ────────────────────────────────────
   Half-height "glance" mode. We compress everything (smaller fonts,
   thinner bars, tighter padding) instead of dropping rows — Session
   + Week stay; refresh / external-link buttons in foot stay; Calendar
   keeps preset chips + textarea + send. Selectors are data-* based
   so drag-resize updates them live. */
.widget-tile[data-w="2"][data-h="1"] .widget-inner {
  padding: 4px 10px;
  gap: 2px;
}
.widget-tile[data-w="2"][data-h="1"] .widget-body { gap: 3px; }
.widget-tile[data-w="2"][data-h="1"] .widget-head strong { font-size: 12px; }
.widget-tile[data-w="2"][data-h="1"] .widget-head .muted { font-size: 10px; }
.widget-tile[data-w="2"][data-h="1"] .widget-row { gap: 1px 8px; }
.widget-tile[data-w="2"][data-h="1"] .widget-row .usage-bar { height: 4px; }
.widget-tile[data-w="2"][data-h="1"] .widget-row-label { font-size: 10px; }
.widget-tile[data-w="2"][data-h="1"] .widget-pct { font-size: 9px; min-width: 22px; }
.widget-tile[data-w="2"][data-h="1"] .widget-foot { padding-top: 2px; font-size: 9px; }
.widget-tile[data-w="2"][data-h="1"] .widget-icon-btn { width: 18px; height: 18px; }
.widget-tile[data-w="2"][data-h="1"] .widget-icon-btn svg { width: 11px; height: 11px; }
/* Hide non-actionable noise: stale banner + extra-credit row. The
   Week row stays; for Claude where there are 1–3 Week rows (all /
   Sonnet / Opus) we keep only the first ("Week · all") and drop the
   per-model breakdown — not legible at this size. */
.widget-tile[data-w="2"][data-h="1"] .widget-stale,
.widget-tile[data-w="2"][data-h="1"] .widget-extra-row {
  display: none;
}
.widget-tile.widget-usage-claude[data-w="2"][data-h="1"] .widget-row-week ~ .widget-row-week {
  display: none;
}
/* Calendar quick-add at 2×1: drop the head (no info worth 22px),
   compress the rest so chips + textarea + send all fit. */
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .widget-head { display: none; }
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-body {
  padding: 4px 6px;
  gap: 4px;
}
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-presets { min-height: 18px; }
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-chip {
  padding: 1px 7px;
  font-size: 10px;
}
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-input {
  min-height: 22px;
  padding: 3px 6px;
  font-size: 11px;
  line-height: 1.3;
}

/* Clipboard widget at 2×1: drop the head, compact buttons + preview. */
.widget-tile.widget-clipboard-quick[data-w="2"][data-h="1"] .widget-head { display: none; }
.widget-tile.widget-clipboard-quick[data-w="2"][data-h="1"] .cbw-body {
  padding: 4px 6px;
  gap: 4px;
}
.widget-tile.widget-clipboard-quick[data-w="2"][data-h="1"] .cbw-btn {
  padding: 6px 4px;
  font-size: 11px;
}
.widget-tile.widget-clipboard-quick[data-w="2"][data-h="1"] .cbw-latest {
  padding: 3px 6px;
  font-size: 10px;
}
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-send {
  padding: 2px 10px;
  font-size: 10px;
}
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-model {
  max-width: 68px;
  padding: 1px 3px;
  font-size: 9px;
}
.widget-tile.widget-calendar-quick[data-w="2"][data-h="1"] .cqw-msg { font-size: 10px; }

/* ── Group widget (container for element strips) ─────────── */
.widget-group .group-head {
  align-items: center;
  gap: 6px;
}
/* In edit mode the .widget-controls overlay sits at top:6px right:6px
   with 3 buttons ([+][📁][×]). Reserve ~78px on the head's right so
   the group name truncates before colliding with them. */
.home-grid.editing .widget-group .group-head {
  padding-right: 78px;
}
.group-head-emoji { font-size: 16px; line-height: 1; flex: 0 0 auto; }
.group-name {
  font-size: 13px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  outline: none;
}
.group-name[contenteditable="true"] {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 4px;
  cursor: text;
}
/* Group widget head is now just emoji + name — all action buttons
   (+ / 📁 / ×) live in .widget-controls overlay so the top-right has
   a single unified visual zone. Old .group-add-btn / .group-add-sub-btn
   / .group-count rules removed along with their template hooks. */
.group-body {
  /* override the gap=6 from .widget-body — strips set their own
     density inside .group-list. */
  overflow-y: auto;
}
.group-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.group-strip {
  position: relative;
  display: flex;
  align-items: center;
  border-radius: 6px;
}
.group-strip:hover { background: var(--bg); }
.group-strip-link {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  text-decoration: none;
  color: var(--fg);
}
.group-strip-name {
  font-size: 12px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.group-strip-del,
.group-strip-grip {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 11px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  display: none;            /* shown only in editing mode */
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
.group-strip-del { right: 2px; color: #dc2626; font-size: 12px; }
.group-strip-grip {
  right: 24px;
  color: var(--fg-muted);
  font-weight: 700;
  letter-spacing: -1px;
  cursor: grab;
}
.group-strip-del:hover { background: #fef2f2; }
.group-strip-grip:hover { background: var(--bg-soft); color: var(--fg); }
.home-grid.editing .group-strip-del,
.home-grid.editing .group-strip-grip { display: inline-flex; }
.home-grid.editing .group-strip-link { padding-right: 46px; }
.home-grid.editing .group-strip {
  /* Touch drag of a strip should not double as page scroll. */
  touch-action: none;
  cursor: grab;
}
.home-grid.editing.dragging .group-strip { cursor: grabbing; }
/* While the user is dragging a strip out, the original stays put as
   a placeholder hint at half-opacity; the actual moving visual is a
   fixed-positioned clone (.group-strip-ghost) appended to <body>
   that can fly outside the widget tile without being clipped by its
   overflow / scroll containers. */
.group-strip.strip-dragging { opacity: 0.3; }
.group-strip-ghost {
  display: flex;
  align-items: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  list-style: none;
  padding: 0;
  box-sizing: border-box;
}
.group-strip-ghost .group-strip-del { display: none; }
/* When something is being dragged onto a group, highlight the drop
   zone. Added/removed by home_grid.js. Same treatment for top-level
   widget tiles and for sub-container strips — the user reads "blue
   dashed = this is where it lands". */
.widget-tile.group-drop-target {
  outline: 2px dashed #2563eb;
  outline-offset: -2px;
  background: rgba(37, 99, 235, 0.05);
}
.group-strip.group-drop-target,
.group-strip-container.group-drop-target {
  outline: 2px dashed #2563eb;
  outline-offset: -2px;
  background: rgba(37, 99, 235, 0.10);
  border-radius: 6px;
}

/* ── Sub-container strips (nested groups inside a group widget) ───
   A sub-container is a group widget referenced from another group's
   items[]. Visually it's a strip with a chevron + emoji + name +
   count. Clicking the strip body (or chevron) toggles a `.is-collapsed`
   class on the strip; the immediately-following `.group-sub-wrapper`
   <li> holds the recursive child list and is hidden via adjacent-sibling
   selector when collapsed. localStorage persists per-container state
   keyed by element id. Each nesting level adds 16px of left padding. */
.group-strip-chevron {
  flex: 0 0 auto;
  width: 14px;
  height: 18px;
  border: none;
  background: transparent;
  color: var(--fg-muted);
  font-size: 10px;
  line-height: 1;
  padding: 0;
  margin-left: 2px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease;
  user-select: none;
}
.group-strip-chevron:hover { color: var(--fg); }
.group-strip-container:not(.is-collapsed) > .group-strip-chevron {
  transform: rotate(90deg);
}
.group-strip-container .group-strip-link-container {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  cursor: pointer;
  color: var(--fg);
}
.group-strip-emoji {
  font-size: 14px;
  line-height: 1;
  flex: 0 0 auto;
  width: 18px;
  text-align: center;
}
.group-strip-link-container .group-strip-name {
  font-size: 12px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Indent each nested level: the <li class="group-sub-wrapper"> holds
   the recursive <ul>; padding-left compounds naturally because each
   wrapper is itself nested inside its parent group's ul. */
.group-sub-wrapper {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}
/* VS Code-style tree connector: a 1px guide line at the left of the
   sub-tree that visually anchors children to their parent container.
   Aligns roughly under the parent strip's chevron, runs from the top
   of the sub-tree to just past the last visible child. */
.group-sub-wrapper::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 0;
  bottom: 4px;
  width: 1px;
  background: var(--border);
  opacity: 0.6;
}
.group-sub-wrapper > .group-list {
  padding-left: 16px;
}
.group-strip-container.is-collapsed + .group-sub-wrapper {
  display: none;
}
/* Sub-container in-strip add buttons — edit-mode only. All four
   right-side buttons (add, add-sub, grip, ×) are absolute-positioned
   so the strip's flex flow stays simple. Slot layout (right edge → left):
     × at right:2,  grip at right:24,
     📁 at right:46, + at right:68.
   In edit mode the link-container gets padding-right: 88px so the
   name truncates before reaching the buttons. */
.group-strip-add,
.group-strip-add-sub {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg-muted);
  font-size: 11px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
}
.group-strip-add { right: 68px; font-size: 14px; }
.group-strip-add-sub { right: 46px; }
.home-grid.editing .group-strip-add,
.home-grid.editing .group-strip-add-sub { display: inline-flex; }
.group-strip-add:hover,
.group-strip-add-sub:hover {
  background: var(--bg-soft);
  color: var(--fg);
}
.home-grid.editing .group-strip-container .group-strip-link {
  padding-right: 88px;
}

/* Suppress native long-press affordances (iOS link callout, macOS
   link preview, default link-as-image drag ghost) so that the
   long-press-to-edit gesture isn't competing with the OS. We keep
   touch-action: auto here — page scrolling needs to work outside
   edit mode; touch-action: none gets layered on only inside .editing
   so a finger drag during reorder doesn't double as a scroll. */
.home-grid .icon-tile,
.home-grid .widget-tile {
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* ── Edit mode visuals ───────────────────────────────────── */
.widget-controls {
  position: absolute;
  top: 6px;
  right: 6px;
  display: none;
  gap: 4px;
  z-index: 2;
}
.widget-ctl-btn {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.widget-ctl-btn:hover { background: var(--bg-soft); }
.widget-ctl-btn.danger { color: #dc2626; }

.home-grid.editing .widget-controls { display: flex; }

/* ── Edge resize handles (group widget, etc.) ───────────
   Drag-strips along the right edge / bottom edge / SE corner of the
   widget tile. Visible only in edit mode. The strips themselves are
   transparent but a small visible "tab" sits in the middle of each
   edge + a chunky corner glyph at the SE corner, so the affordance
   is discoverable without the user having to memorize the geometry.
   Pointer cursors confirm on hover; the lift / snap is handled by
   home_grid.js via [data-widget-resize].

   Hit area is generous (14 px / 18 px corner) — narrower strips are
   technically correct but get missed by trackpad users. */
.widget-resize-handle {
  position: absolute;
  display: none;
  z-index: 3;
  background: transparent;
  touch-action: none;
}
.widget-resize-r {
  top: 0;
  right: 0;
  bottom: 0;
  width: 14px;
  cursor: ew-resize;
}
.widget-resize-b {
  left: 0;
  right: 0;
  bottom: 0;
  height: 14px;
  cursor: ns-resize;
}
.widget-resize-br {
  right: 0;
  bottom: 0;
  width: 22px;
  height: 22px;
  cursor: nwse-resize;
}
.home-grid.editing .widget-resize-handle { display: block; }
/* Visible "grip tab" in the middle of each edge in edit mode — gives
   the user a target to aim for. ::after instead of ::before so it
   sits over the transparent hit area. */
.home-grid.editing .widget-resize-r::after,
.home-grid.editing .widget-resize-b::after {
  content: "";
  position: absolute;
  background: rgba(37, 99, 235, 0.45);
  border-radius: 999px;
  transition: background 0.12s;
}
.home-grid.editing .widget-resize-r::after {
  /* vertical pill on right edge */
  top: 50%;
  right: 4px;
  width: 3px;
  height: 28px;
  transform: translateY(-50%);
}
.home-grid.editing .widget-resize-b::after {
  /* horizontal pill on bottom edge */
  left: 50%;
  bottom: 4px;
  width: 28px;
  height: 3px;
  transform: translateX(-50%);
}
.home-grid.editing .widget-resize-br::after {
  /* two short slashes ⌝ at the SE corner */
  content: "";
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 10px;
  height: 10px;
  background:
    linear-gradient(135deg, transparent 60%, rgba(37, 99, 235, 0.6) 60%, rgba(37, 99, 235, 0.6) 75%, transparent 75%, transparent 85%, rgba(37, 99, 235, 0.6) 85%);
}
.home-grid.editing .widget-resize-handle:hover {
  background: rgba(37, 99, 235, 0.18);
}
.home-grid.editing .widget-resize-handle:hover::after {
  background: rgba(37, 99, 235, 0.95);
}
.home-grid.editing .widget-tile.widget-resizing {
  /* Pause the jiggle and lift the tile while the user is actively
     dragging an edge — same elevation cue used by drag-reorder. */
  z-index: 1000;
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.home-grid.editing .widget-tile.widget-resizing .tile-inner {
  animation-play-state: paused;
  transform: none;
}

/* ── Drag-drop overlay ───────────────────────────────────
   Full-viewport panel that appears while the user is dragging a URL
   or folder into the page. Pointer-events: none on the outer overlay
   so the drag continues to dispatch events to the page beneath until
   the user releases. */
.home-dropzone {
  position: fixed;
  inset: 0;
  z-index: 9000;
  background: rgba(15, 23, 42, 0.18);
  backdrop-filter: blur(3px);
  display: none;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.home-dropzone.active { display: flex; }
.home-dropzone-card {
  background: var(--bg);
  border: 2px dashed rgba(99, 102, 241, 0.75);
  border-radius: 16px;
  padding: 32px 48px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--fg);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
}
.home-dropzone-card svg {
  width: 44px;
  height: 44px;
  color: rgba(99, 102, 241, 0.85);
  margin-bottom: 4px;
}
.home-dropzone-card strong { font-size: 15px; }
.home-dropzone-card span { font-size: 12px; color: var(--fg-muted); }
.home-grid.editing .icon-tile,
.home-grid.editing .widget-tile {
  touch-action: none;            /* prevent page scroll during drag */
  cursor: grab;
}
.home-grid.editing.dragging .icon-tile,
.home-grid.editing.dragging .widget-tile { cursor: grabbing; }

/* Jiggle animation — on the inner wrapper so the outer tile can
   freely take FLIP `transform` for smooth reorder transitions. */
@keyframes home-jiggle {
  0%, 100% { transform: rotate(-0.7deg); }
  50%      { transform: rotate( 0.7deg); }
}
.home-grid.editing .tile-inner {
  animation: home-jiggle 0.32s ease-in-out infinite;
  transform-origin: center;
}
.home-grid.editing .icon-tile:nth-child(odd)  .tile-inner { animation-delay: -0.16s; }
.home-grid.editing .icon-tile:nth-child(even) .tile-inner { animation-delay: 0s; }
.home-grid.editing .widget-tile:nth-child(odd)  .tile-inner { animation-delay: 0s; }
.home-grid.editing .widget-tile:nth-child(even) .tile-inner { animation-delay: -0.16s; }
.home-grid.editing.dragging .tile-inner { animation-play-state: paused; transform: none; }

/* ── Pinned tiles ────────────────────────────────────────────
   data-pin="hard": locked (no drag/resize, jiggle disabled, 📌 icon).
   data-pin="soft": remembered cell from a prior pin; the user can
   still drag, so no special styling beyond the explicit grid cell
   that the inline style provides.
*/
.home-grid [data-pin="hard"] { cursor: default !important; }
.home-grid.editing [data-pin="hard"] .tile-inner {
  animation: none !important;
  transform: none !important;
}
.home-grid [data-pin="hard"]::before {
  content: "📌";
  position: absolute;
  top: 2px;
  left: 4px;
  font-size: 10px;
  line-height: 1;
  z-index: 5;
  pointer-events: none;
  opacity: 0.7;
  filter: grayscale(0.2);
}
.home-grid.editing .widget-tile[data-pin="hard"]:not(.widget-group) .widget-controls,
.home-grid.editing .widget-tile[data-pin="hard"] .widget-resize-handle {
  display: none;
}

/* Lifted item during drag. The outer tile is z-elevated + scaled. */
.home-grid .reorder-lifted {
  z-index: 1000;
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
  opacity: 0.95;
}

/* ── Add-widget dialog ───────────────────────────────────── */
.modal-dlg {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  max-width: 520px;
  width: 90%;
  background: var(--bg);
  color: var(--fg);
}
.modal-dlg::backdrop { background: rgba(0,0,0,0.35); }
.modal-dlg h3 { margin: 0 0 4px 0; }
.modal-dlg menu {
  margin: 16px 0 0 0;
  padding: 0;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.widget-catalog {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}
.widget-catalog-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-soft);
}
.widget-size-picker { display: flex; gap: 6px; }
.widget-size-btn {
  padding: 4px 10px;
  font-size: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg);
  cursor: pointer;
}
.widget-size-btn:hover { background: var(--bg-soft); }
