// VELOUR dashboard — Rituals pillar page. Sequences you DO: checkable habits, streaks, time-anchored.
// The onboarding heart of the product: reshape the starter rituals, add your own, attach optional tracking.
const { Card, ProgressBar, Badge } = window.VelourDesignSystem_380ed4;

// ---- Default starter state (disposable — users clear these and add their own) ----
const FRESH = !!window.VELOUR_FRESH;
const STARTER_SEQUENCES = [
  {
    id: "morning", name: "Morning ritual", time: "7:00 AM", icon: "sun",
    habits: [
      { id: "h-bed", libId: "bed", label: "Make the bed", icon: "bed", streak: 21 },
      { id: "h-dress", libId: "dress", label: "Dress with intention", icon: "coat-hanger", streak: 14 },
      { id: "h-affirm", libId: "affirm", label: "Morning affirmation", icon: "quotes", streak: 9, fresh: true },
      { id: "h-walk", libId: "walk", label: "Morning walk", icon: "footprints", streak: 41, trackType: "distance", track: { value: 2.4, unit: "km" } },
    ],
  },
  {
    id: "evening", name: "Evening wind-down", time: "9:30 PM", icon: "moon-stars",
    habits: [
      { id: "h-skin", libId: "skin", label: "Skincare routine", icon: "sparkle", streak: 18 },
      { id: "h-screens", libId: "screens", label: "No screens after 9", icon: "phone-x", streak: 6 },
      { id: "h-read", libId: "read", label: "Read 20 minutes", icon: "book-open", streak: 8 },
      { id: "h-journal", libId: "journal", label: "Three lines, journaled", icon: "notebook", streak: 0 },
    ],
  },
];
if (FRESH) STARTER_SEQUENCES.forEach((s) => { s.habits = []; });

// ---- Suggestion library, tuned per ritual. track = optional logging it supports ----
const HABIT_LIBRARY = {
  morning: [
    { id: "bed", label: "Make the bed", icon: "bed" },
    { id: "dress", label: "Dress with intention", icon: "coat-hanger" },
    { id: "affirm", label: "Morning affirmation", icon: "quotes" },
    { id: "walk", label: "Morning walk", icon: "footprints", track: "distance" },
    { id: "vitamins", label: "Cold water + vitamins", icon: "pill", track: "supplements" },
    { id: "hydrate", label: "Hydrate", icon: "drop", track: "hydration" },
    { id: "stretch", label: "Stretch & mobility", icon: "barbell", track: "duration" },
    { id: "meditate", label: "Meditate", icon: "flower-lotus", track: "duration" },
    { id: "journal-am", label: "Journal a page", icon: "notebook" },
    { id: "sunlight", label: "Step into sunlight", icon: "sun" },
    { id: "nophone", label: "No phone, first hour", icon: "phone-x" },
    { id: "intention", label: "Set the day's intention", icon: "list-checks" },
  ],
  evening: [
    { id: "skin", label: "Skincare routine", icon: "sparkle" },
    { id: "screens", label: "No screens after 9", icon: "phone-x" },
    { id: "read", label: "Read 20 minutes", icon: "book-open" },
    { id: "journal", label: "Three lines, journaled", icon: "notebook" },
    { id: "stretch-pm", label: "Evening stretch", icon: "barbell", track: "duration" },
    { id: "breath", label: "Breathwork", icon: "wind", track: "duration" },
    { id: "tea", label: "Herbal tea", icon: "coffee" },
    { id: "dim", label: "Dim the lights", icon: "lightbulb" },
    { id: "tomorrow", label: "Tomorrow's plan", icon: "list-checks" },
    { id: "tidy", label: "Tidy for ten minutes", icon: "broom" },
    { id: "gratitude", label: "Name one gratitude", icon: "heart" },
    { id: "clothes", label: "Lay out tomorrow's clothes", icon: "coat-hanger" },
  ],
};

const TRACK_META = {
  distance:    { icon: "ruler", verb: "Note distance" },
  supplements: { icon: "pill",  verb: "Log your supplements" },
  hydration:   { icon: "drop",  verb: "Track hydration" },
  duration:    { icon: "timer", verb: "Note duration" },
};

const DEFAULT_TRACK = {
  distance:    { value: 2.4, unit: "km" },
  supplements: { items: ["Vitamin D", "B12", "Magnesium"] },
  hydration:   { mode: "bottle", brand: null, glasses: 6 },
  duration:    { mins: 10 },
};

// Kept in sync with SUPP_PRESETS in DashWellness.jsx so supplement choices match across pillars.
const SUPPLEMENT_SUGGESTIONS = [
  "Vitamin D", "Vitamin C", "Vitamin B12", "Multivitamin",
  "Magnesium", "Omega-3", "Probiotic", "Zinc",
  "Iron", "Calcium", "Creatine", "Collagen",
  "Ashwagandha", "Melatonin", "Turmeric", "Electrolytes",
];
const BOTTLE_BRANDS = ["HidrateSpark", "Ulla", "LARQ"];

// ---- small reusable round stepper button -----------------------------------
function RoundBtn({ icon, onClick, label }) {
  return (
    <button onClick={onClick} aria-label={label} style={{ width: 28, height: 28, borderRadius: "50%", flexShrink: 0, border: "var(--border-width) solid var(--border-strong)", background: "var(--surface-card)", color: "var(--text-secondary)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-sans)" }}>
      <i className={`ph ph-${icon}`} style={{ fontSize: 13 }}></i>
    </button>
  );
}

function trackSummary(type, t) {
  if (type === "distance") return `${t.value} ${t.unit} · today`;
  if (type === "duration") return `${t.mins} min`;
  if (type === "supplements") return t.items.length ? t.items.join(" · ") : "Log your supplements";
  if (type === "hydration") {
    if (t.mode === "bottle") return t.brand ? `${t.brand} · syncs automatically` : "Choose how to track";
    return `${t.glasses} glasses · ~${window.velourLitres(t.glasses * 0.24)} L`;
  }
  return "";
}

// ---- tracking editors -------------------------------------------------------
function TrackingEditor({ type, track, onChange }) {
  const [supInput, setSupInput] = React.useState("");
  const meta = TRACK_META[type];

  let body = null;
  if (type === "distance") {
    const step = (d) => onChange({ ...track, value: Math.max(0, +(track.value + d).toFixed(1)) });
    body = (
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <RoundBtn icon="minus" label="Less" onClick={() => step(-0.1)} />
          <span style={{ fontFamily: "var(--font-serif)", fontSize: 21, color: "var(--text-primary)", minWidth: 84, textAlign: "center" }}>{track.value} {track.unit}</span>
          <RoundBtn icon="plus" label="More" onClick={() => step(0.1)} />
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          {["km", "mi"].map((u) => (
            <button key={u} onClick={() => onChange({ ...track, unit: u })} style={{ padding: "6px 14px", borderRadius: 999, border: "none", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500, background: track.unit === u ? "var(--sage)" : "var(--surface-card)", color: track.unit === u ? "#fff" : "var(--text-secondary)" }}>{u}</button>
          ))}
        </div>
      </div>
    );
  } else if (type === "duration") {
    const step = (d) => onChange({ ...track, mins: Math.max(1, track.mins + d) });
    body = (
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <RoundBtn icon="minus" label="Less" onClick={() => step(-1)} />
        <span style={{ fontFamily: "var(--font-serif)", fontSize: 21, color: "var(--text-primary)", minWidth: 80, textAlign: "center" }}>{track.mins} min</span>
        <RoundBtn icon="plus" label="More" onClick={() => step(1)} />
        <span style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginLeft: 4 }}>logged when you finish</span>
      </div>
    );
  } else if (type === "supplements") {
    const add = (name) => { const v = name.trim(); if (v && !track.items.includes(v)) onChange({ ...track, items: [...track.items, v] }); setSupInput(""); };
    const remove = (name) => onChange({ ...track, items: track.items.filter((i) => i !== name) });
    const remaining = SUPPLEMENT_SUGGESTIONS.filter((s) => !track.items.includes(s));
    body = (
      <div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 7, marginBottom: track.items.length ? 12 : 0 }}>
          {track.items.map((s) => (
            <span key={s} style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "6px 8px 6px 12px", borderRadius: 999, background: "var(--surface-card)", border: "var(--border-width) solid var(--border)", fontSize: 13, color: "var(--text-primary)" }}>
              {s}
              <button onClick={() => remove(s)} aria-label={`Remove ${s}`} style={{ display: "flex", border: "none", background: "none", cursor: "pointer", color: "var(--text-tertiary)", padding: 0 }}><i className="ph ph-x" style={{ fontSize: 12 }}></i></button>
            </span>
          ))}
        </div>
        <div style={{ display: "flex", gap: 8, marginBottom: remaining.length ? 12 : 0 }}>
          <input value={supInput} onChange={(e) => setSupInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") add(supInput); }} placeholder="Add a supplement…" style={{ flex: 1, padding: "9px 13px", borderRadius: 10, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-primary)", outline: "none" }} />
          <button onClick={() => add(supInput)} style={{ padding: "0 16px", borderRadius: 10, border: "none", background: "var(--sage)", color: "#fff", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 500, cursor: "pointer" }}>Add</button>
        </div>
        {remaining.length > 0 && (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {remaining.map((s) => (
              <button key={s} className="vr-chip" onClick={() => add(s)} style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 11px", borderRadius: 999, border: "var(--border-width) dashed var(--border-strong)", background: "transparent", cursor: "pointer", fontSize: 12.5, color: "var(--text-secondary)", fontFamily: "var(--font-sans)" }}><i className="ph ph-plus" style={{ fontSize: 10 }}></i>{s}</button>
            ))}
          </div>
        )}
      </div>
    );
  } else if (type === "hydration") {
    const setMode = (mode) => onChange({ ...track, mode });
    const stepGlasses = (d) => onChange({ ...track, glasses: Math.max(1, track.glasses + d) });
    body = (
      <div>
        <div style={{ display: "flex", gap: 6, marginBottom: 14, background: "var(--surface-card)", borderRadius: 999, padding: 4, width: "fit-content" }}>
          {[["bottle", "Smart bottle"], ["manual", "Log manually"]].map(([m, lbl]) => (
            <button key={m} onClick={() => setMode(m)} style={{ padding: "7px 15px", borderRadius: 999, border: "none", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500, background: track.mode === m ? "var(--sage)" : "transparent", color: track.mode === m ? "#fff" : "var(--text-secondary)" }}>{lbl}</button>
          ))}
        </div>
        {track.mode === "bottle" ? (
          <div>
            <div style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginBottom: 9 }}>Connect a bottle and hydration syncs on its own — nothing to tap.</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
              {BOTTLE_BRANDS.map((b) => (
                <button key={b} onClick={() => onChange({ ...track, brand: track.brand === b ? null : b })} style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 14px", borderRadius: 12, cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 500, border: "var(--border-width) solid " + (track.brand === b ? "var(--sage)" : "var(--border)"), background: track.brand === b ? "var(--sage-fill)" : "var(--surface-card)", color: track.brand === b ? "var(--sage-text)" : "var(--text-primary)" }}>
                  <i className={`ph ph-${track.brand === b ? "check-circle" : "drop"}`} style={{ fontSize: 15, color: track.brand === b ? "var(--sage)" : "var(--text-tertiary)" }}></i>{b}
                </button>
              ))}
            </div>
            {track.brand && (
              <div style={{ display: "flex", alignItems: "center", gap: 9, marginTop: 12, fontSize: 13, color: "var(--sage-text)" }}>
                <i className="ph ph-bluetooth" style={{ fontSize: 15, color: "var(--sage)" }}></i>{track.brand} connected · syncs automatically
              </div>
            )}
          </div>
        ) : (
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <RoundBtn icon="minus" label="Fewer" onClick={() => stepGlasses(-1)} />
            <span style={{ fontFamily: "var(--font-serif)", fontSize: 21, color: "var(--text-primary)", minWidth: 130, textAlign: "center" }}>{track.glasses} glasses</span>
            <RoundBtn icon="plus" label="More" onClick={() => stepGlasses(1)} />
            <span style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginLeft: 4 }}>≈ {window.velourLitres(track.glasses * 0.24)} L</span>
          </div>
        )}
      </div>
    );
  }

  return (
    <div style={{ marginLeft: 37, marginTop: 10, background: "var(--sage-fill)", borderRadius: 13, padding: "15px 17px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 13 }}>
        <i className={`ph ph-${meta.icon}`} style={{ fontSize: 15, color: "var(--sage)" }}></i>
        <span style={{ fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--sage-text)", fontWeight: 500, flex: 1 }}>{meta.verb}</span>
        <span style={{ fontSize: 11.5, color: "var(--sage-text)", opacity: 0.7 }}>Optional</span>
      </div>
      {body}
    </div>
  );
}

// ---- one habit row ----------------------------------------------------------
function HabitRow({ habit, first, editing, onToggle, onRemove, onOpenEditor, onCloseEditor, onAttachTrack, onTrackChange }) {
  return (
    <div className="vr-habit" style={{ padding: "13px 24px", borderTop: first ? "none" : "var(--border-width) solid var(--divider)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 13 }}>
        <button onClick={onToggle} style={{ display: "flex", alignItems: "center", gap: 13, flex: 1, minWidth: 0, textAlign: "left", background: "none", border: "none", cursor: "pointer", fontFamily: "var(--font-sans)", padding: 0 }}>
          <span style={{ width: 24, height: 24, borderRadius: "50%", flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", background: habit.done ? (habit.fresh ? "var(--teal)" : "var(--sage)") : "transparent", border: habit.done ? "none" : "1.5px solid var(--border-strong)" }}>
            {habit.done && <i className="ph ph-check" style={{ fontSize: 13, color: "#fff" }}></i>}
          </span>
          <span style={{ flex: 1, fontSize: 14.5, color: habit.done ? "var(--text-tertiary)" : "var(--text-primary)", textDecoration: habit.done ? "line-through" : "none", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{habit.label}</span>
        </button>
        {habit.streak > 0
          ? <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5, color: "var(--sage-text)", whiteSpace: "nowrap", flexShrink: 0 }}><i className="ph ph-seal-check" style={{ fontSize: 13, color: "var(--sage)" }}></i>{habit.streak}d held</span>
          : <span style={{ fontSize: 12.5, color: "var(--text-tertiary)", whiteSpace: "nowrap", flexShrink: 0 }}>{habit.isNew ? "New" : "—"}</span>}
        <button className="vr-remove" onClick={onRemove} aria-label={`Remove ${habit.label}`} style={{ display: "flex", border: "none", background: "none", cursor: "pointer", color: "var(--text-tertiary)", padding: 4, flexShrink: 0 }}><i className="ph ph-x" style={{ fontSize: 14 }}></i></button>
      </div>

      {/* tracking affordance / summary / editor */}
      {habit.trackType && !editing && (
        habit.track
          ? <button onClick={onOpenEditor} style={{ display: "inline-flex", alignItems: "center", gap: 7, marginLeft: 37, marginTop: 7, background: "var(--sage-fill)", border: "none", borderRadius: 9, padding: "5px 11px", color: "var(--sage-text)", fontSize: 12.5, cursor: "pointer", fontFamily: "var(--font-sans)" }}>
              <i className={`ph ph-${TRACK_META[habit.trackType].icon}`} style={{ fontSize: 13 }}></i>
              {trackSummary(habit.trackType, habit.track)}
              <i className="ph ph-sliders" style={{ fontSize: 12, opacity: 0.6, marginLeft: 1 }}></i>
            </button>
          : <button className="vr-afford" onClick={onAttachTrack} style={{ display: "inline-flex", alignItems: "center", gap: 6, marginLeft: 37, marginTop: 5, background: "none", border: "none", padding: "2px 0", color: "var(--text-tertiary)", fontSize: 12.5, cursor: "pointer", fontFamily: "var(--font-sans)", transition: "color var(--dur-base) var(--ease-out)" }}>
              <i className="ph ph-plus" style={{ fontSize: 11 }}></i>{TRACK_META[habit.trackType].verb}
            </button>
      )}
      {habit.trackType && editing && (
        <div>
          <TrackingEditor type={habit.trackType} track={habit.track || DEFAULT_TRACK[habit.trackType]} onChange={onTrackChange} />
          <button onClick={onCloseEditor} style={{ display: "inline-flex", alignItems: "center", gap: 6, marginLeft: 37, marginTop: 9, background: "var(--ink)", color: "var(--parchment)", border: "none", borderRadius: 999, padding: "7px 16px", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500, cursor: "pointer" }}><i className="ph ph-check" style={{ fontSize: 13 }}></i>Done</button>
        </div>
      )}
    </div>
  );
}

// ---- the picker (opens under "Add to this ritual") --------------------------
function HabitPicker({ seqId, seqName, habits, onAdd, onClose }) {
  const [custom, setCustom] = React.useState("");
  const library = HABIT_LIBRARY[seqId] || [];
  const addCustom = () => { const v = custom.trim(); if (v) { onAdd({ label: v }); setCustom(""); } };
  return (
    <div style={{ borderTop: "var(--border-width) solid var(--divider)", background: "var(--surface-sunken)", padding: "20px 24px" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
        <span style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--sage-text)", fontWeight: 500 }}>Add to {seqName}</span>
        <button onClick={onClose} aria-label="Close" style={{ display: "flex", border: "none", background: "none", cursor: "pointer", color: "var(--text-tertiary)", padding: 2 }}><i className="ph ph-x" style={{ fontSize: 16 }}></i></button>
      </div>
      <div style={{ display: "flex", gap: 8, marginBottom: 18 }}>
        <input value={custom} onChange={(e) => setCustom(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") addCustom(); }} placeholder="One small act, in your words…" style={{ flex: 1, padding: "11px 14px", borderRadius: 11, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-primary)", outline: "none" }} />
        <button onClick={addCustom} style={{ padding: "0 18px", borderRadius: 11, border: "none", background: "var(--ink)", color: "var(--parchment)", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 500, cursor: "pointer", whiteSpace: "nowrap" }}>Add ritual</button>
      </div>
      <div style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginBottom: 11 }}>Or start from a suggestion</div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
        {library.map((s) => {
          const added = habits.some((h) => h.libId === s.id);
          return (
            <button key={s.id} className={added ? "" : "vr-chip"} disabled={added} onClick={() => onAdd(s)} style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "9px 14px", borderRadius: 999, cursor: added ? "default" : "pointer", fontFamily: "var(--font-sans)", fontSize: 13.5, border: "var(--border-width) solid " + (added ? "transparent" : "var(--border)"), background: added ? "var(--sage-fill)" : "var(--surface-card)", color: added ? "var(--sage-text)" : "var(--text-primary)", opacity: added ? 0.7 : 1, transition: "border-color var(--dur-base) var(--ease-out)" }}>
              <i className={`ph ph-${added ? "check" : s.icon}`} style={{ fontSize: 15, color: added ? "var(--sage)" : "var(--text-tertiary)" }}></i>
              {s.label}
              {s.track && !added && <span style={{ fontSize: 10.5, letterSpacing: "0.04em", textTransform: "uppercase", color: "var(--sage-text)", background: "var(--sage-fill)", borderRadius: 999, padding: "2px 7px", fontWeight: 500 }}>{s.track === "distance" ? "distance" : s.track === "supplements" ? "log" : s.track === "hydration" ? "sync" : "time"}</span>}
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ---- a full ritual card -----------------------------------------------------
// ---- a full ritual card -----------------------------------------------------
// `seq.habits` is the template (which habits exist). `doneMap` is { habitId: true } for the selected day.
function SequenceCard({ seq, doneMap, onToggle, onAdd, onRemove, onTrackChange, onAttachTrack, atLimit, maxRituals, onNavigate }) {
  const [picking, setPicking] = React.useState(false);
  const [editingId, setEditingId] = React.useState(null);
  const habits = seq.habits.map((h) => ({ ...h, done: !!doneMap[h.id] }));
  const done = habits.filter((h) => h.done).length;

  return (
    <Card padding={0} style={{ overflow: "hidden", display: "flex", flexDirection: "column" }}>
      <div style={{ padding: "22px 24px", borderBottom: "var(--border-width) solid var(--divider)" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 12, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--sage-text)", fontWeight: 500 }}>
            <i className={`ph ph-${seq.icon}`} style={{ fontSize: 15, color: "var(--sage)" }}></i>{seq.time}
          </span>
          <span style={{ fontSize: 13, color: "var(--text-tertiary)", whiteSpace: "nowrap" }}>{habits.length ? `${done} of ${habits.length}` : "Empty"}</span>
        </div>
        <div style={{ fontFamily: "var(--font-serif)", fontSize: 23, color: "var(--text-primary)", letterSpacing: "-0.01em", margin: "4px 0 14px" }}>{seq.name}</div>
        <ProgressBar value={done} max={Math.max(habits.length, 1)} pillar="rituals" size="sm" />
      </div>

      <div>
        {habits.length === 0 && (
          <div style={{ padding: "28px 24px", textAlign: "center", color: "var(--text-tertiary)", fontSize: 14, lineHeight: 1.6 }}>
            A blank ritual. Add the first habit that belongs in your {seq.name.toLowerCase()}.
          </div>
        )}
        {habits.map((h, i) => (
          <HabitRow
            key={h.id} habit={h} first={i === 0} editing={editingId === h.id}
            onToggle={() => onToggle(seq.id, h.id)}
            onRemove={() => onRemove(seq.id, h.id)}
            onOpenEditor={() => setEditingId(h.id)}
            onCloseEditor={() => setEditingId(null)}
            onAttachTrack={() => { onAttachTrack(seq.id, h.id, h.trackType); setEditingId(h.id); }}
            onTrackChange={(t) => onTrackChange(seq.id, h.id, t)}
          />
        ))}

        {atLimit ? (
          // The wall, stated where the add button was. It names the limit, what
          // lifting it costs nothing to ask about, and leaves what they already
          // have untouched — removing a ritual frees the slot back up.
          <div style={{ padding: "18px 24px", borderTop: habits.length ? "var(--border-width) solid var(--divider)" : "none", display: "flex", alignItems: "flex-start", gap: 12 }}>
            <span style={{ width: 24, height: 24, borderRadius: "50%", flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "var(--ink)", marginTop: 1 }}><i className="ph ph-lock-simple" style={{ fontSize: 12, color: "var(--parchment)" }}></i></span>
            <div>
              <div style={{ fontSize: 14.5, color: "var(--text-primary)", fontWeight: 500 }}>Your free plan holds {maxRituals} rituals.</div>
              <p style={{ fontSize: 13.5, color: "var(--text-secondary)", lineHeight: 1.55, margin: "4px 0 0" }}>
                Premium lifts the limit. You can also remove one to make room — nothing you've built goes away.
              </p>
              <button onClick={() => window.velourOpenPricing(onNavigate)} style={{ marginTop: 10, background: "none", border: "none", padding: 0, cursor: "pointer", color: "var(--sage-text)", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 6 }}>
                See what Premium adds <i className="ph ph-arrow-right" style={{ fontSize: 13 }}></i>
              </button>
            </div>
          </div>
        ) : !picking ? (
          <button onClick={() => setPicking(true)} style={{ width: "100%", textAlign: "left", display: "inline-flex", alignItems: "center", gap: 12, padding: "16px 24px", background: "none", border: "none", borderTop: habits.length ? "var(--border-width) solid var(--divider)" : "none", cursor: "pointer", color: "var(--sage-text)", fontFamily: "var(--font-sans)", fontSize: 14.5, fontWeight: 500 }}>
            <span style={{ width: 24, height: 24, borderRadius: "50%", flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", border: "1.5px dashed var(--sage)" }}><i className="ph ph-plus" style={{ fontSize: 12, color: "var(--sage)" }}></i></span>
            Add to this ritual
          </button>
        ) : (
          <HabitPicker seqId={seq.id} seqName={seq.name} habits={seq.habits} onAdd={(s) => { onAdd(seq.id, s); }} onClose={() => setPicking(false)} />
        )}
      </div>
    </Card>
  );
}

// ---- date helpers -------------------------------------------------------------
function rtToISODate(d) { return window.velourDateISO(d); }
function rtTodayISO() {
  const d = new Date();
  const off = Number(window.VELOUR_DAY_OFFSET) || 0;
  if (off) d.setDate(d.getDate() + off);
  return rtToISODate(d);
}
// The member's own clock, "HH:MM", 24-hour and zero-padded — the shape migration
// 019 stores and the only thing the chain needs to order one day's steps against
// each other. Deliberately NOT a timestamp: ordering is a within-day question, and
// a UTC instant would put a 10pm ritual on tomorrow, the same trap the day key
// avoids by being local.
function rtNowHM() {
  const d = new Date();
  return String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
}
function rtFormatDateLabel(iso) {
  const today = rtTodayISO();
  const d = new Date(iso + "T00:00:00");
  // Neighbours derive from the same "today" so the labels can't disagree with it.
  if (iso === today) return "Today";
  if (iso === rtAddDays(today, -1)) return "Yesterday";
  if (iso === rtAddDays(today, 1)) return "Tomorrow";
  return d.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" });
}
function rtAddDays(iso, n) {
  const d = new Date(iso + "T00:00:00");
  d.setDate(d.getDate() + n);
  return rtToISODate(d);
}

// ---- page -------------------------------------------------------------------
const SEQ_STORAGE_KEY = FRESH ? "velour_fresh_rituals_sequences" : "velour_rituals_sequences";
const LOG_STORAGE_KEY = FRESH ? "velour_fresh_rituals_log" : "velour_rituals_log";

function loadSequences() {
  try {
    const saved = localStorage.getItem(SEQ_STORAGE_KEY);
    if (saved) return JSON.parse(saved);
  } catch (e) {}
  return STARTER_SEQUENCES;
}

function loadLog() {
  try {
    const saved = localStorage.getItem(LOG_STORAGE_KEY);
    if (saved) return JSON.parse(saved);
  } catch (e) {}
  return {}; // { "2026-06-16": { "h-bed": true, ... } }
}

function saveSequences(seqs) {
  try { localStorage.setItem(SEQ_STORAGE_KEY, JSON.stringify(seqs)); } catch (e) {}
}

// THE CHAIN LATCH, 13 Sep 2026. A chain is proven the first time every ritual in
// a sequence is held on the same day — but the log records no date a ritual was
// ADDED, so re-checking the current list against history meant adding one new
// step to a proven sequence sent the card straight back to "Example, not yours
// yet". A member was being told they had lost something for building on it.
// Once proven, the chain stays; the ritual ids held that day are kept so steps
// added since can be marked as not yet held with the rest.
//
// LOCAL ONLY, on purpose. Sync's reconcile writes the server's sequence objects
// over the local ones, so a field hung on a sequence would be wiped on the next
// pull — and the server has no column for it. Losing this key costs nothing
// false: the card falls back to checking the log, as it did before.
//
// STAMPED WITH ITS OWNER, because sequence ids ("morning") and seeded ritual ids
// ("h-bed") are the same on every account. Sign-out clears the key (the rituals
// purge in _ds_bundle.js), but a session abandoned without signing out does not
// run that purge — see the owner note there. A latch owned by somebody else is
// ignored. An unowned one was written before an account existed and is carried
// in, the same rule reconcile applies to anonymous ritual data.
const CHAIN_STORAGE_KEY = FRESH ? "velour_fresh_rituals_chains_v1" : "velour_rituals_chains_v1";

function rtCurrentUid() {
  try {
    const s = window.velourAuth && window.velourAuth.session();
    return (s && s.user && s.user.id) || null;
  } catch (e) { return null; }
}

function loadChainLatch() {
  try {
    const doc = JSON.parse(localStorage.getItem(CHAIN_STORAGE_KEY) || "null");
    if (!doc || typeof doc !== "object" || !doc.chains || typeof doc.chains !== "object") return {};
    if (doc.owner && doc.owner !== rtCurrentUid()) return {};
    return doc.chains;
  } catch (e) { return {}; }
}

function saveChainLatch(chains) {
  try { localStorage.setItem(CHAIN_STORAGE_KEY, JSON.stringify({ owner: rtCurrentUid(), chains })); } catch (e) {}
}

// The latest day every ritual currently in the sequence was held, or null.
function rtLastFullDay(seq, log) {
  const habits = seq.habits || [];
  if (habits.length < 2) return null;
  const days = Object.keys(log).filter((d) => habits.every((h) => (log[d] || {})[h.id])).sort();
  return days.length ? days[days.length - 1] : null;
}
function saveLog(log) {
  try { localStorage.setItem(LOG_STORAGE_KEY, JSON.stringify(log)); } catch (e) {}
}

// The longest run of consecutive days this habit was ever held, anywhere in the
// log — independent of whether that run is still alive today.
function longestRun(log, habitId) {
  const days = Object.keys(log).filter((d) => log[d] && log[d][habitId]).sort();
  let best = 0;
  let run = 0;
  let prev = null;
  for (const d of days) {
    run = prev && rtAddDays(prev, 1) === d ? run + 1 : 1;
    if (run > best) best = run;
    prev = d;
  }
  return best;
}

// Compute a per-habit streak: consecutive days (ending at `untilISO`) the habit was marked done.
function computeStreak(log, habitId, untilISO) {
  let streak = 0;
  let cursor = untilISO;
  while (log[cursor] && log[cursor][habitId]) {
    streak += 1;
    cursor = rtAddDays(cursor, -1);
  }
  return streak;
}

// Headline and support used to gate on two unrelated conditions — the hold length
// and the count of full mornings — so the pair could openly contradict itself:
// "Nothing held yet" sitting above "You've held 1 of the last 7 mornings in full",
// and a named two-day hold sitting above the never-logged-anything empty state.
// Both now read from one assessment of the same history.
function RitualsInsight({ hold, morningsHeld, totalHabits }) {
  const hasHold = hold.streak >= 2;
  const hasHistory = hasHold || morningsHeld > 0;

  const headline = hasHold
    ? `${hold.label} — ${hold.streak} days running. Quietly compounding.`
    : morningsHeld > 0
      ? "A morning held in full. That is where the pattern starts."
      : "Nothing held yet — that's expected. The first check-in starts the hold.";

  const support = morningsHeld > 0
    ? `You've held ${morningsHeld} of the last 7 mornings in full. A quiet day in between doesn't undo any of it.`
    : hasHistory
      ? "No full morning this week. What compounds is the hold above, not the perfect day."
      : totalHabits > 0
        ? "Check off what you complete and the pattern shows here after a few days."
        : "Add one small ritual to a sequence — VELOUR starts noticing from the first check-in.";

  return <window.VelourInsight pillar="rituals" headline={headline} support={support} cite="automaticity-66-days" />;
}

function DashRituals({ onNavigate }) {
  const [sequences, setSequences] = React.useState(loadSequences);
  const [log, setLog] = React.useState(loadLog);
  const [selectedDate, setSelectedDate] = React.useState(rtTodayISO());
  const [justSaved, setJustSaved] = React.useState(false);
  const plan = window.useVelourPlan ? window.useVelourPlan() : { maxRituals: null };

  // Sync writes localStorage; this screen read it once, at mount. On a second
  // device the pull lands after first paint, so without this the member sees an
  // empty account until they reload.
  const [chainLatch, setChainLatch] = React.useState(loadChainLatch);
  if (window.useVelourSynced) window.useVelourSynced("rituals", () => {
    setSequences(loadSequences());
    setLog(loadLog());
    setChainLatch(loadChainLatch());
  });

  const today = rtTodayISO();
  const isToday = selectedDate === today;
  const isFuture = selectedDate > today;

  const persistSequences = (next) => { setSequences(next); saveSequences(next); };
  const persistLog = (next) => { setLog(next); saveLog(next); };

  // Toggle a habit's done state for the SELECTED day only.
  const toggleHabit = (seqId, habitId) => {
    setJustSaved(false);
    const dayMap = { ...(log[selectedDate] || {}) };
    // Checking a ritual records WHEN, so the chain can show the order a day was
    // actually held in rather than the order it was arranged in. Truthy either
    // way, so every other reader is untouched (see migration 019).
    //
    // ONLY FOR TODAY. Ticking a box on Sunday for something held on Friday knows
    // nothing about Friday's clock, and stamping it with now would be a confident
    // invention — exactly what this feature exists to replace. Backfilled days stay
    // `true`, which reads as "held, time unknown", and the chain falls back.
    //
    // An already-held ritual keeps its original time: unchecking clears it, and
    // re-checking stamps afresh, but a re-save must never overwrite the first one.
    dayMap[habitId] = dayMap[habitId] ? false : (selectedDate === rtTodayISO() ? rtNowHM() : true);
    persistLog({ ...log, [selectedDate]: dayMap });
  };

  // The free plan's ritual limit counts every habit across both sequences, not
  // per-sequence — "three rituals" means three things you do, wherever they sit.
  // Counted here rather than from `totalHabits` below, which is derived further
  // down the component than addHabit needs it.
  const maxRituals = plan.maxRituals;
  const atRitualLimit = maxRituals != null && sequences.reduce((n, sq) => n + sq.habits.length, 0) >= maxRituals;

  // Habit list edits (add/remove/track) affect the template — same across all days.
  const addHabit = (seqId, s) => {
    if (atRitualLimit) return;
    const id = "h-" + Math.random().toString(36).slice(2, 8);
    const next = sequences.map((sq) => sq.id === seqId
      ? { ...sq, habits: [...sq.habits, { id, libId: s.id, label: s.label, icon: s.icon || "circle", streak: 0, trackType: s.track || null, track: null, isNew: true }] }
      : sq);
    persistSequences(next);
  };
  const removeHabit = (seqId, habitId) => {
    const next = sequences.map((sq) => sq.id === seqId ? { ...sq, habits: sq.habits.filter((h) => h.id !== habitId) } : sq);
    persistSequences(next);
    // Clean up any logged completions for the removed habit across all days.
    const nextLog = {};
    Object.keys(log).forEach((d) => { const dm = { ...log[d] }; delete dm[habitId]; nextLog[d] = dm; });
    persistLog(nextLog);
  };
  const attachTrack = (seqId, habitId, type) => {
    const next = sequences.map((sq) => sq.id === seqId ? { ...sq, habits: sq.habits.map((h) => h.id === habitId ? { ...h, track: { ...DEFAULT_TRACK[type] } } : h) } : sq);
    persistSequences(next);
  };
  const changeTrack = (seqId, habitId, t) => {
    const next = sequences.map((sq) => sq.id === seqId ? { ...sq, habits: sq.habits.map((h) => h.id === habitId ? { ...h, track: t } : h) } : sq);
    persistSequences(next);
  };

  const handleSaveDay = () => {
    saveLog(log); // already persisted on every toggle, but this gives an explicit confirm
    setJustSaved(true);
    setTimeout(() => setJustSaved(false), 2200);
  };

  const dayMap = log[selectedDate] || {};
  const allHabits = sequences.flatMap((s) => s.habits);
  const totalHabits = allHabits.length;

  // Two different questions, and they were previously answered by one number.
  //
  // "Longest held" is the longest run in the member's history. It used to be
  // computeStreak(..., today), which is the run ending TODAY — so the morning
  // after a two-day hold the tile read "—  Just getting started", as though the
  // history had been erased. A record you can lose by waking up is not a record.
  const longest = allHabits.reduce((best, h) => {
    const s = longestRun(log, h.id);
    return s > best.streak ? { streak: s, label: h.label } : best;
  }, { streak: 0, label: "—" });

  // The live run, still anchored to today — this is the one the insight can call
  // "days running", because that phrasing is only true of a hold that is alive.
  const current = allHabits.reduce((best, h) => {
    const s = computeStreak(log, h.id, today);
    return s > best.streak ? { streak: s, label: h.label } : best;
  }, { streak: 0, label: "—" });

  const last7 = Array.from({ length: 7 }, (_, i) => rtAddDays(today, -i));
  const morningsHeld = last7.filter((d) => {
    const dm = log[d] || {};
    const morningHabits = sequences.find((s) => s.id === "morning")?.habits || [];
    return morningHabits.length > 0 && morningHabits.every((h) => dm[h.id]);
  }).length;

  // The insight prefers the live computed hold; the demo's seeded per-habit
  // streak values stand in until the log has real history.
  const seedBest = allHabits.reduce((b, h) => ((h.streak || 0) > b.streak ? { streak: h.streak, label: h.label } : b), { streak: 0, label: "—" });
  const insightHold = current.streak >= seedBest.streak ? current : seedBest;
  // The tile prefers the seed for the same reason the insight does. Without this
  // the demo showed "Longest held —  Just getting started" directly beside an
  // insight reading "41 days running", because the tile only ever counted the log
  // and Jennifer's showcase carries seeded holds rather than a year of check-ins.
  // On a real account seeded holds are 0, so this changes nothing there.
  const longestShown = longest.streak >= seedBest.streak ? longest : seedBest;

  const stats = [
    { label: "Longest held", value: longestShown.streak > 0 ? `${longestShown.streak} day${longestShown.streak === 1 ? "" : "s"}` : "—", sub: longestShown.streak > 0 ? longestShown.label : "Just getting started" },
    { label: "This week", value: `${morningsHeld} of 7`, sub: "Mornings held" },
    { label: "Rituals forming", value: String(totalHabits), sub: `Across ${sequences.length} sequence${sequences.length === 1 ? "" : "s"}` },
  ];

  // THE REAL CHAIN, 12 Sep 2026. "Ritual chains" used to be permanent fiction —
  // three hardcoded example habits ("Morning walk", "Cold water + vitamins"),
  // shown to every member forever, describing a mechanism ("VELOUR starts
  // chaining them") that never actually engaged. A member asked whether the
  // arrows between the steps were supposed to be clickable — which is the tell:
  // it read as more built than it was.
  //
  // The log has no per-check timestamp — a day only records which habits were
  // checked, not when — so "the order they're actually completed in" cannot be
  // computed and must not be faked. What CAN be shown honestly is the member's
  // own configured order for a sequence they have proven out: same predicate as
  // "a morning held in full" everywhere else in the product (8ac035c) — every
  // habit in the sequence checked on at least one real day — generalized to
  // any sequence, not only the morning one. Multi-habit and proven at least
  // once is "settled in"; the milestone is the reveal, not a rolling window.
  //
  // Latched since 13 Sep (see CHAIN_STORAGE_KEY): a sequence proven once keeps
  // its chain while at least two of the rituals held that day are still in it.
  // Rituals added since are listed in place and marked, never hidden and never
  // passed off as already held with the rest.
  const chainFor = (s) => {
    const habits = s.habits || [];
    if (habits.length < 2) return null;
    if (rtLastFullDay(s, log)) return { seq: s, added: [] };
    const latch = chainLatch[s.id];
    if (!latch || !Array.isArray(latch.ids)) return null;
    const heldIds = new Set(latch.ids);
    if (habits.filter((h) => heldIds.has(h.id)).length < 2) return null;
    return { seq: s, added: habits.filter((h) => !heldIds.has(h.id)).map((h) => h.id) };
  };
  const chain = sequences.reduce((found, s) => found || chainFor(s), null);
  const chainSequence = chain ? chain.seq : null;
  const chainAdded = new Set(chain ? chain.added : []);

  // THE ORDER IT WAS ACTUALLY HELD IN, 16 Sep 2026 — the member's own idea, after
  // she read the old card as claiming this and asked what happens when the
  // assumption is wrong. It was assuming; now it either knows or says it doesn't.
  //
  // WEB ONLY, AND THAT IS A DECISION RATHER THAN AN OMISSION (owner, 16 Sep 2026).
  // Mobile RECORDS the times — app.jsx writes the same "HH:MM" and it syncs — but
  // it deliberately grows no chain card of its own: looking back over how a week
  // actually went is a sit-down thing, and the two surfaces are allowed to differ
  // where the surface suits the moment. So this is NOT a parity gap to be closed;
  // check with the owner before "fixing" it. The recording half IS parity, and has
  // to stay: most rituals are held on the phone, so without it this card would have
  // almost nothing to read.
  //
  // Needs a day where EVERY step carries a time (migration 019 records "HH:MM" as
  // the log value from the moment a ritual is checked). Any step still holding the
  // legacy `true` means that day predates the times, or was backfilled, and a
  // partial order is worse than none: it would silently mix real evidence with the
  // arranged order and present the result as observed. So it is all or nothing.
  //
  // The most recent qualifying day wins rather than an average across days. People
  // genuinely vary, and "here is how you did it on Tuesday" is a true sentence,
  // where "here is the order you tend to use" would need more days than anyone has
  // yet and a rule for what counts as tending. That generalisation can come later,
  // from the same data — which is the point of recording it now.
  const heldOrder = React.useMemo(() => {
    if (!chainSequence) return null;
    const ids = (chainSequence.habits || []).map((h) => h.id);
    if (ids.length < 2) return null;
    const days = Object.keys(log).sort().reverse();
    for (const day of days) {
      const dm = log[day] || {};
      const times = ids.map((id) => dm[id]);
      if (!times.every((t) => typeof t === "string" && /^\d{2}:\d{2}$/.test(t))) continue;
      const ordered = ids.slice().sort((a, b) => (dm[a] < dm[b] ? -1 : dm[a] > dm[b] ? 1 : 0));
      // An order identical to the arranged one is not worth a different sentence:
      // the card would claim a discovery while showing exactly what it showed
      // before. Say it plainly only when the day actually differed.
      const differs = ordered.some((id, i) => id !== ids[i]);
      return { day, ids: ordered, differs, at: dm };
    }
    return null;
  }, [chainSequence, log]);
  const chainSteps = heldOrder
    ? heldOrder.ids.map((id) => (chainSequence.habits || []).find((h) => h.id === id)).filter(Boolean)
    : (chainSequence ? chainSequence.habits : []);

  // Record a latch the moment a full day exists, and move it forward when a
  // later full day covers a different set of rituals — that is what clears the
  // "added since" marks once a new step has been held with the rest.
  React.useEffect(() => {
    let changed = false;
    const next = { ...chainLatch };
    sequences.forEach((s) => {
      const day = rtLastFullDay(s, log);
      if (!day) return;
      const ids = s.habits.map((h) => h.id);
      const prev = next[s.id];
      if (prev && prev.on === day && JSON.stringify(prev.ids) === JSON.stringify(ids)) return;
      next[s.id] = { on: day, ids };
      changed = true;
    });
    if (changed) { setChainLatch(next); saveChainLatch(next); }
  }, [sequences, log]);

  return (
    <main style={{ maxWidth: 1240, margin: "0 auto", padding: "36px 32px 64px", display: "flex", flexDirection: "column", gap: 24, fontFamily: "var(--font-sans)" }}>
      <style>{`
        /* HOVER IS NOT AVAILABLE ON A PHONE, and asking for it costs a tap.
           Safari and Chrome answer the first tap on an element with :hover rules
           by applying the hover state instead of dispatching the click, so a
           control revealed by hover needs tapping twice. Hover styling is gated
           to pointers that can actually hover; touch gets the revealed state
           outright. :focus-visible stays ungated — keyboard focus is unrelated. */
        .vr-remove { opacity: 0; transition: opacity .15s var(--ease-out); }
        @media (hover: hover) and (pointer: fine) {
          .vr-habit:hover .vr-remove { opacity: .55; }
          .vr-remove:hover { opacity: 1 !important; color: var(--text-primary) !important; }
          .vr-afford:hover { color: var(--sage-text) !important; }
          .vr-chip:hover { border-color: var(--sage) !important; }
        }
        @media (hover: none) { .vr-remove { opacity: .55; } }
      `}</style>

      {/* Header */}
      <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", flexWrap: "wrap", gap: 24 }}>
        <div style={{ maxWidth: 560 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 9, fontSize: 11.5, letterSpacing: "0.16em", textTransform: "uppercase", fontWeight: 500, color: "var(--sage-text)", background: "var(--sage-fill)", borderRadius: 999, padding: "6px 14px" }}><i className="ph ph-sun" style={{ fontSize: 14 }}></i>Rituals</span>
          <h1 style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 40, lineHeight: 1.08, letterSpacing: "-0.02em", color: "var(--text-primary)", margin: "18px 0 0", textWrap: "balance" }}>Make these rituals your own.</h1>
          <p style={{ fontSize: 16, lineHeight: 1.65, color: "var(--text-secondary)", margin: "14px 0 0" }}>Two sequences to start you off — keep what fits, remove what doesn't, and add the rituals that are actually yours. A few can quietly track the details: distance on a walk, your supplements, hydration from a smart bottle.</p>
        </div>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
          {stats.map((s) => (
            <div key={s.label} style={{ background: "var(--sage-fill)", borderRadius: 14, padding: "16px 18px", minWidth: 132, flex: "1 1 132px" }}>
              <div style={{ fontSize: 11.5, color: "var(--sage-text)", opacity: 0.8, marginBottom: 8 }}>{s.label}</div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 26, color: "var(--sage-text)", letterSpacing: "-0.01em", whiteSpace: "nowrap" }}>{s.value}</div>
              <div style={{ fontSize: 12, color: "var(--sage-text)", opacity: 0.75, marginTop: 4 }}>{s.sub}</div>
            </div>
          ))}
        </div>
      </div>

      <RitualsInsight hold={insightHold} morningsHeld={morningsHeld} totalHabits={totalHabits} />

      {/* Date selector + save bar */}
      <Card padding={0} style={{ overflow: "hidden" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "16px 22px", flexWrap: "wrap", gap: 14 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <button onClick={() => setSelectedDate((d) => rtAddDays(d, -1))} aria-label="Previous day" style={{ width: 34, height: 34, borderRadius: "50%", border: "var(--border-width) solid var(--border-strong)", background: "var(--surface-card)", color: "var(--text-secondary)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <i className="ph ph-caret-left" style={{ fontSize: 14 }}></i>
            </button>

            <div style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 200 }}>
              <i className="ph ph-calendar-blank" style={{ fontSize: 17, color: "var(--sage)" }}></i>
              <span style={{ fontFamily: "var(--font-serif)", fontSize: 19, color: "var(--text-primary)", letterSpacing: "-0.01em" }}>{rtFormatDateLabel(selectedDate)}</span>
              <input
                type="date"
                value={selectedDate}
                onChange={(e) => e.target.value && setSelectedDate(e.target.value)}
                style={{ border: "var(--border-width) solid var(--border)", borderRadius: 8, padding: "5px 9px", fontSize: 12.5, color: "var(--text-tertiary)", background: "var(--surface-card)", fontFamily: "var(--font-sans)" }}
              />
            </div>

            <button onClick={() => setSelectedDate((d) => rtAddDays(d, 1))} aria-label="Next day" style={{ width: 34, height: 34, borderRadius: "50%", border: "var(--border-width) solid var(--border-strong)", background: "var(--surface-card)", color: "var(--text-secondary)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <i className="ph ph-caret-right" style={{ fontSize: 14 }}></i>
            </button>

            {!isToday && (
              <button onClick={() => setSelectedDate(today)} style={{ fontSize: 13, color: "var(--sage-text)", background: "var(--sage-fill)", border: "none", borderRadius: 999, padding: "7px 14px", cursor: "pointer", fontFamily: "var(--font-sans)", fontWeight: 500 }}>
                Jump to today
              </button>
            )}
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            {justSaved && (
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13.5, color: "var(--sage-text)" }}>
                <i className="ph ph-check-circle" style={{ fontSize: 16 }}></i>Saved {rtFormatDateLabel(selectedDate).toLowerCase()}
              </span>
            )}
            <button onClick={handleSaveDay} style={{ display: "inline-flex", alignItems: "center", gap: 8, background: "var(--ink)", color: "var(--parchment)", border: "none", borderRadius: 999, padding: "11px 22px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 500, cursor: "pointer", boxShadow: "0 4px 14px rgba(44,44,42,0.18)" }}>
              <i className="ph ph-floppy-disk" style={{ fontSize: 15 }}></i>
              Save {rtFormatDateLabel(selectedDate).toLowerCase()}'s rituals
            </button>
          </div>
        </div>
        <div style={{ background: "var(--sage-fill)", padding: "9px 22px", fontSize: 12.5, color: "var(--sage-text)", borderTop: "var(--border-width) solid var(--border)" }}>
          {isToday
            ? "Each day starts fresh — check off what you complete, and it's saved automatically as you go."
            : `Editing ${rtFormatDateLabel(selectedDate).toLowerCase()}. Changes here only affect that day's log.`}
        </div>
      </Card>

      {/* Sequences */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 260px), 1fr))", gap: 24, alignItems: "start" }}>
        {sequences.map((seq) => (
          <SequenceCard
            key={seq.id}
            seq={seq}
            doneMap={dayMap}
            onToggle={toggleHabit}
            onAdd={addHabit}
            onRemove={removeHabit}
            onAttachTrack={attachTrack}
            onTrackChange={changeTrack}
            atLimit={atRitualLimit}
            maxRituals={maxRituals}
            onNavigate={onNavigate}
          />
        ))}
      </div>

      {/* Reflection — the real chain once a sequence has proven itself, an
          honestly-labeled example until then. Never both read as the same
          thing: a member should never wonder which one they're looking at. */}
      {chainSequence ? (
        <Card variant="featured" pillar="rituals" padding={26} style={{ display: "flex", gap: 22, alignItems: "flex-start", flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 320px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <i className="ph ph-link" style={{ fontSize: 18, color: "var(--sage)" }}></i>
              <span style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--sage-text)", fontWeight: 500 }}>Your ritual chain</span>
            </div>
            {/* "has settled into an order" was read by a member on 16 Sep as the
                product claiming it had WATCHED the order — "it's taking the order
                of the rituals and assuming they're being done in order". It has
                not, and it cannot: the log records which rituals were held on a
                day, never when (see THE REAL CHAIN above). The paragraph below
                always said "in the order you built them", but the headline led
                and the headline overclaimed. It now says whose order this is. */}
            <div style={{ fontFamily: "var(--font-serif)", fontSize: 22, lineHeight: 1.32, color: "var(--text-primary)", letterSpacing: "-0.01em" }}>
              {heldOrder
                ? `Your ${chainSequence.name.toLowerCase()}, in the order you held it ${rtFormatDateLabel(heldOrder.day).toLowerCase()}.`
                : `Your ${chainSequence.name.toLowerCase()} is settled in — here it is, in the order you arranged it.`}
            </div>
            {/* No word implying VELOUR pushes or nudges between steps: nothing in
                the product runs in the background to do that (see "How VELOUR
                runs"), and promising a mechanism that doesn't exist is the exact
                mistake this card is being rebuilt to stop making. */}
            <p style={{ fontSize: 14.5, color: "var(--text-secondary)", marginTop: 10, lineHeight: 1.6, maxWidth: 460 }}>
              {heldOrder
                ? (heldOrder.differs
                    ? "This is the order you actually held them in, which is not the order they are listed in — and that is fine. VELOUR follows you rather than the other way round."
                    : "This is the order you actually held them in, taken from the times each one was checked rather than from how they are listed.")
                : "These are your own steps, in the order you built them — not an example. Doing them one after another is what tends to make each one easier than starting cold."}
            </p>
            {chainAdded.size > 0 && (
              <p style={{ fontSize: 13.5, color: "var(--text-tertiary)", marginTop: 10, lineHeight: 1.6, maxWidth: 460 }}>Steps marked new were added after this chain settled. Hold them alongside the rest on one day and they join it.</p>
            )}
          </div>
          <div style={{ flex: "1 1 240px", display: "flex", flexDirection: "column", gap: 10 }}>
            {chainSteps.map((h, i, arr) => {
              const isAdded = chainAdded.has(h.id);
              const heldTime = heldOrder ? heldOrder.at[h.id] : null;
              return (
                <div key={h.id} style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <span style={{ width: 28, height: 28, borderRadius: "50%", flexShrink: 0, background: isAdded ? "var(--surface-sunken)" : "var(--sage-fill)", color: isAdded ? "var(--text-tertiary)" : "var(--sage-text)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12.5, fontWeight: 500 }}>{i + 1}</span>
                  <span style={{ flex: 1, fontSize: 14, color: isAdded ? "var(--text-secondary)" : "var(--text-primary)" }}>
                    {h.label}
                    {isAdded && <span style={{ marginLeft: 8, fontSize: 10.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--text-tertiary)" }}>New</span>}
                  </span>
                  {/* The evidence, shown rather than described. A member who is told
                      this is the order she held them in should be able to check. */}
                  {heldTime && !isAdded && <span style={{ fontSize: 12, color: "var(--text-tertiary)", fontVariantNumeric: "tabular-nums" }}>{heldTime}</span>}
                  {i < arr.length - 1 && <i className="ph ph-arrow-down" style={{ fontSize: 14, color: "var(--text-tertiary)" }}></i>}
                </div>
              );
            })}
          </div>
        </Card>
      ) : (
        <Card variant="featured" pillar="rituals" padding={26} style={{ display: "flex", gap: 22, alignItems: "flex-start", flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 320px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
              <i className="ph ph-link" style={{ fontSize: 18, color: "var(--sage)" }}></i>
              <span style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--sage-text)", fontWeight: 500 }}>Ritual chains</span>
            </div>
            <div style={{ fontFamily: "var(--font-serif)", fontSize: 22, lineHeight: 1.32, color: "var(--text-primary)", letterSpacing: "-0.01em" }}>Once a sequence settles in, this becomes your own chain.</div>
            <p style={{ fontSize: 14.5, color: "var(--text-secondary)", marginTop: 10, lineHeight: 1.6, maxWidth: 460 }}>Build a sequence of two or more rituals and hold every one of them on the same day once — the panel on the right will swap to your own steps, in the order you set them. Until then, here's what that looks like.</p>
          </div>
          {/* Marked as an example on the panel itself, not only in the paragraph
              beside it — the paragraph is easy to skip past, and a member who
              only sees the numbered list is the one who asked whether the
              arrows were supposed to do something. */}
          <div style={{ flex: "1 1 240px", display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ fontSize: 10.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--text-tertiary)", marginBottom: 2 }}>Example, not yours yet</div>
            {["Morning walk", "Cold water + vitamins", "Set the day's intention"].map((t, i, arr) => (
              <div key={t} style={{ display: "flex", alignItems: "center", gap: 12, opacity: 0.7 }}>
                <span style={{ width: 28, height: 28, borderRadius: "50%", flexShrink: 0, background: "var(--surface-sunken)", color: "var(--text-tertiary)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12.5, fontWeight: 500 }}>{i + 1}</span>
                <span style={{ flex: 1, fontSize: 14, color: "var(--text-secondary)" }}>{t}</span>
                {i < arr.length - 1 && <i className="ph ph-arrow-down" style={{ fontSize: 14, color: "var(--text-tertiary)" }}></i>}
              </div>
            ))}
          </div>
        </Card>
      )}
    </main>
  );
}

window.DashRituals = DashRituals;
