/*
 * RitualOS components.
 *
 * Two naming tiers, deliberately:
 *
 *   - Existing global names that are already the shared vocabulary and have no conflicting
 *     private definition are KEPT and extended: .btn, .form-group, .form-control, .glass-card.
 *     Extending them breaks nothing.
 *   - Anything where a conflicting private definition already exists gets a NEW z- name.
 *     .status-badge has three incompatible definitions across the pages and stat cards have
 *     five sibling vocabularies; reusing those names would mean hoping specificity wins.
 *     A new name makes each page's migration a mechanical find-and-replace with a visible diff.
 *
 * Everything lives in @layer components, so the pages' own inline <style> blocks (unlayered)
 * still win until they are deleted. See the layer note at the top of tokens.css.
 */

@layer components {

  /* ══ icon ═════════════════════════════════════════════════════════════════════════════════
     The inline glyph ritual_icon() / zIcon() emit. Sized by the font so it sits in a label
     like a character; a --size on the parent, or the modifier classes, make it bigger. */
  .z-icon {
    width: var(--icon-size, 1.1em);
    height: var(--icon-size, 1.1em);
    flex: none;
    vertical-align: -0.2em;
  }
  .z-icon--lg { --icon-size: 1.5em; }
  .z-icon--xl { --icon-size: 2em; }

  /* ══ tone utilities ═══════════════════════════════════════════════════════════════════════
     Shared by badge, stat, bar and list so a status colour is expressed one way everywhere. */
  .is-accent  { --tone: var(--accent);  --tone-soft: var(--accent-soft);  --tone-line: var(--accent-line);  --tone-text: var(--accent-text); }
  .is-success { --tone: var(--success); --tone-soft: var(--success-soft); --tone-line: var(--success-line); --tone-text: var(--success-text); }
  .is-danger  { --tone: var(--danger);  --tone-soft: var(--danger-soft);  --tone-line: var(--danger-line);  --tone-text: var(--danger-text); }
  .is-warning { --tone: var(--warning); --tone-soft: var(--warning-soft); --tone-line: var(--warning-line); --tone-text: var(--warning-text); }
  .is-info    { --tone: var(--info);    --tone-soft: var(--info-soft);    --tone-line: var(--info-line);    --tone-text: var(--info-text); }
  .is-neutral { --tone: var(--text-muted); --tone-soft: var(--surface-raise-2); --tone-line: var(--border-default); --tone-text: var(--text-secondary); }


  /* ══ display type ═════════════════════════════════════════════════════════════════════════
     A named scale for the moments a life OS should speak up, rather than every page inventing
     its own font-size. .z-numeric is separate because figures that update must not reflow --
     tabular-nums keeps every digit the same width. */
  .z-display {
    font-family: var(--font-display);
    font-size: var(--fs-display);
    font-weight: var(--fw-bold);
    line-height: 1.02;
    letter-spacing: -0.03em;
  }
  .z-hero-type {
    font-family: var(--font-display);
    font-size: var(--fs-hero);
    font-weight: var(--fw-semi);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-tight);
  }
  .z-lead {
    font-size: var(--fs-lg);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
  }
  .z-overline {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-muted);
  }
  .z-caption { font-size: var(--fs-xs); color: var(--text-muted); }
  .z-numeric {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
  }
  /* A figure that should read as lit, for the single most important number on a screen. */
  .z-display--glow { text-shadow: 0 0 30px var(--accent-glow), var(--halo); }

  /* ══ card ═════════════════════════════════════════════════════════════════════════════════ */
  .card {
    background: var(--surface-1);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    padding: var(--sp-5);
    box-shadow: var(--shadow-1);
  }

  .card--flush   { padding: 0; overflow: hidden; }
  .card--inset   { background: var(--surface-raise-1); box-shadow: none; }
  .card--raised  { background: var(--surface-2); box-shadow: var(--shadow-2); }
  .card--accent  { border-color: var(--accent-line); background:
                   linear-gradient(180deg, var(--accent-softer), transparent 60%), var(--surface-1); }

  .card--interactive {
    transition: transform var(--dur) var(--ease-out),
                border-color var(--dur) var(--ease-out),
                box-shadow var(--dur) var(--ease-out);
  }
  .card--interactive:hover {
    transform: translateY(-2px);
    border-color: var(--accent-line);
    box-shadow: var(--shadow-3);
  }

  .card__head {
    display: flex; align-items: center; gap: var(--sp-3);
    margin: calc(var(--sp-5) * -1) calc(var(--sp-5) * -1) var(--sp-4);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border-subtle);
  }
  .card__title   { font-size: var(--fs-lg); }
  .card__sub     { color: var(--text-muted); font-size: var(--fs-md); }
  .card__actions { margin-left: auto; display: flex; gap: var(--sp-2); }
  .card__foot {
    margin: var(--sp-4) calc(var(--sp-5) * -1) calc(var(--sp-5) * -1);
    padding: var(--sp-4) var(--sp-5);
    border-top: 1px solid var(--border-subtle);
    display: flex; gap: var(--sp-2); align-items: center;
  }


  /* ══ page head ════════════════════════════════════════════════════════════════════════════
     Replaces eighteen byte-identical hero blocks, each a .glass-card with an inline
     linear-gradient and a hardcoded indigo tint that ignored the user's accent. */
  .page-head {
    display: flex; align-items: center; gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-6);
    margin-bottom: var(--sp-6);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: linear-gradient(135deg, var(--surface-1) 0%, var(--accent-soft) 140%);
    box-shadow: var(--shadow-1);
  }
  .page-head__icon  { font-size: 1.9rem; line-height: 1; flex: none; }
  .page-head__title { font-size: var(--fs-2xl); }
  .page-head__sub   { color: var(--text-muted); font-size: var(--fs-md); margin-top: 0.15rem; }
  .page-head__actions { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }

  @media (max-width: 639px) {
    .page-head { flex-wrap: wrap; padding: var(--sp-4); }
    .page-head__actions { margin-left: 0; width: 100%; }
  }


  /* ══ stat ═════════════════════════════════════════════════════════════════════════════════
     One vocabulary replacing five: .stat-card-box + .admin-stats-box (admin.php, borrowed by
     employee.php), .stat (finances), .stat-card/.stat-num (household), .stat-card-premium
     (memento), .metric-box/.metric-number (reviews). */
  .z-stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--sp-4);
  }

  .z-stat {
    display: flex; flex-direction: column; gap: var(--sp-1);
    padding: var(--sp-4);
    background: var(--surface-1);
    border: 1px solid var(--border-default);
    border-left: 3px solid var(--tone, var(--accent));
    border-radius: var(--r-md);
  }
  a.z-stat { text-decoration: none; transition: border-color var(--dur) var(--ease-out), background var(--dur) var(--ease-out); }
  a.z-stat:hover { background: var(--surface-2); border-color: var(--border-strong); text-decoration: none; }

  .z-stat__label { font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--tracking-wide); }
  .z-stat__value {
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-semi);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-tight);
    font-variant-numeric: tabular-nums;
  }
  .z-stat__value small { font-size: 0.5em; color: var(--text-muted); margin-left: 0.1em; }
  .z-stat__delta { font-size: var(--fs-xs); color: var(--tone-text, var(--text-muted)); }
  .z-stat__foot  { font-size: var(--fs-xs); color: var(--text-muted); }

  /* A trend needs its own colour. The line above inherits --tone-text from the tone class on
     the CARD, and .z-stat also carries border-left: 3px solid var(--tone) -- so tinting a delta
     green through a tone class would paint a green rail down the whole card and claim something
     about the metric that the trend alone does not support. These colour the delta and nothing
     else. `up` and `down` mean good and bad, not the arrow's direction: spending less is down
     and green. */
  .z-stat__delta--up   { color: var(--success-text); }
  .z-stat__delta--down { color: var(--danger-text); }
  .z-stat__delta--flat { color: var(--text-muted); }

  /* Sparkline, drawn by zSpark() in assets/js/ui.js. The SVG strokes in currentColor, so the
     colour lives here and a theme switch needs no redraw. */
  .z-spark {
    display: block;
    margin-top: var(--sp-2);
    color: var(--tone, var(--accent));
    opacity: 0.85;
  }
  .z-spark svg { display: block; width: 100%; height: 24px; overflow: visible; }
  .z-spark.is-neutral { color: var(--text-muted); }
  .z-spark.is-success { color: var(--success); }
  .z-spark.is-danger  { color: var(--danger); }
  .z-spark.is-warning { color: var(--warning); }
  .z-spark.is-info    { color: var(--info); }
  .z-spark.is-accent  { color: var(--accent); }

  .z-stat--hero .z-stat__value { font-size: 2.6rem; }
  .z-stat--compact { padding: var(--sp-3); }
  .z-stat--compact .z-stat__value { font-size: var(--fs-xl); }


  /* ══ badge ════════════════════════════════════════════════════════════════════════════════
     .status-badge had 88 uses and three conflicting definitions with different modifier
     names. The compat block at the bottom of this file maps the old ones onto these. */
  .z-badge {
    display: inline-flex; align-items: center; gap: 0.35em;
    padding: 0.2rem 0.55rem;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    line-height: 1.5;
    border-radius: var(--r-full);
    background: var(--tone-soft, var(--surface-raise-2));
    color: var(--tone-text, var(--text-secondary));
    border: 1px solid var(--tone-line, var(--border-default));
    white-space: nowrap;
  }
  .z-badge--outline { background: transparent; }
  .z-badge--dot::before {
    content: '';
    width: 0.45em; height: 0.45em;
    border-radius: var(--r-full);
    background: var(--tone, var(--text-muted));
  }


  /* ══ progress bar ═════════════════════════════════════════════════════════════════════════
     .bar-container/.bar-track/.bar-fill/.bar-meta had 21 uses across the app and were defined
     nowhere at all, so the dashboard XP bar rendered as three bare divs. Width comes from a
     --pct custom property rather than an inline width, which keeps the markup declarative. */
  .z-bar { display: flex; flex-direction: column; gap: var(--sp-2); }

  .z-bar__meta {
    display: flex; justify-content: space-between; align-items: baseline;
    font-size: var(--fs-xs); color: var(--text-muted);
  }
  .z-bar__meta strong { color: var(--text-primary); font-variant-numeric: tabular-nums; }

  .z-bar__track {
    height: 8px;
    background: var(--surface-inset);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    overflow: hidden;
  }
  .z-bar__fill {
    height: 100%;
    width: var(--pct, 0%);
    border-radius: inherit;
    background: var(--tone, var(--accent-grad));
    transition: width var(--dur-slow) var(--ease-out);
  }
  .z-bar--thin .z-bar__track { height: 5px; }
  .z-bar--xp   .z-bar__track { height: 12px; }
  .z-bar--xp   .z-bar__fill  { background: var(--accent-grad); box-shadow: 0 0 12px -2px var(--accent-glow); }


  /* ══ progress ring ════════════════════════════════════════════════════════════════════════
     The same reading as .z-bar where the number is the point rather than the trend -- leave
     remaining, a renewal countdown, a key result. It lifts the shape .tk-ring had in
     lib/taskui.css, which nothing ever emitted, into the shared layer: a ring is not a task-UI
     idea, and a page wanting one should not have to claim the task library to get it.

     The label is content on ::after rather than a child element, so the whole control is ONE
     node and drops into a .z-stat, a .z-tile or a table cell without disturbing their layout.
     Percentage arrives as --pct, matching .z-bar, so the markup stays declarative. */
  .z-ring {
    --pct: 0;
    --ring-size: 44px;
    width: var(--ring-size); height: var(--ring-size);
    border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    background: conic-gradient(var(--ring, var(--accent)) calc(var(--pct) * 1%), var(--surface-inset) 0);
    transition: background var(--dur-slow) var(--ease-out);
  }
  .z-ring::after {
    content: attr(data-label);
    width: calc(var(--ring-size) - 10px); height: calc(var(--ring-size) - 10px);
    border-radius: 50%;
    background: var(--surface-2);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--fs-2xs); font-weight: var(--fw-semi);
    color: var(--text-primary); font-variant-numeric: tabular-nums;
  }
  .z-ring.is-success { --ring: var(--success); }
  .z-ring.is-danger  { --ring: var(--danger); }
  .z-ring.is-warning { --ring: var(--warning); }
  .z-ring.is-info    { --ring: var(--info); }
  .z-ring.is-neutral { --ring: var(--text-muted); }
  .z-ring--sm { --ring-size: 34px; }
  .z-ring--lg { --ring-size: 72px; }
  .z-ring--lg::after { font-size: var(--fs-sm); }


  /* ══ tabs ═════════════════════════════════════════════════════════════════════════════════
     One shell replacing fifteen prefixed copies (.corp-*, .admin-*, .house-*, .game-*, …).
     Adopting the rail variant is also what gives employee.php a styled sidebar: it renders
     .corp-* markup whose rules only ever existed inside corporate.php. */
  .z-tabs__nav {
    display: flex; gap: var(--sp-1);
    border-bottom: 1px solid var(--border-default);
    overflow-x: auto;
    scrollbar-width: none;
  }
  .z-tabs__nav::-webkit-scrollbar { display: none; }

  .z-tabs__btn {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    background: none; border: none;
    border-bottom: 2px solid transparent;
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
  }
  .z-tabs__btn:hover { color: var(--text-primary); }
  .z-tabs__btn.is-active { color: var(--accent-text); border-bottom-color: var(--accent); }

  .z-tabs__panel { display: none; }
  .z-tabs__panel.is-active { display: block; animation: z-fade var(--dur) var(--ease-out); }
  @keyframes z-fade { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

  /* pill: a segmented row rather than an underline */
  .z-tabs--pill .z-tabs__nav { border-bottom: none; gap: var(--sp-2); padding: var(--sp-1); background: var(--surface-raise-1); border-radius: var(--r-md); }
  .z-tabs--pill .z-tabs__btn { border-bottom: none; border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-4); }
  .z-tabs--pill .z-tabs__btn.is-active { background: var(--accent-soft); color: var(--accent-text); box-shadow: inset 0 0 0 1px var(--accent-line); }

  /* rail: vertical sidebar plus panel area */
  .z-tabs--rail {
    display: grid;
    grid-template-columns: 232px minmax(0, 1fr);
    gap: var(--sp-5);
    align-items: start;
    transition: grid-template-columns 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .z-tabs--rail.is-collapsed {
    grid-template-columns: 60px minmax(0, 1fr);
  }

  .z-tabs--rail .z-tabs__nav {
    flex-direction: column; gap: var(--sp-1);
    border-bottom: none; overflow: visible;
    background: var(--surface-raise-1);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    padding: var(--sp-2);
    position: sticky; top: calc(var(--topbar-h) + var(--sp-4));
    transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), padding 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  }

  /* Hamburger header on the left */
  .z-tabs__rail-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-2) var(--sp-2);
    margin-bottom: var(--sp-1);
    border-bottom: 1px solid var(--border-subtle);
  }
  .z-tabs__rail-title {
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .z-tabs__hamburger {
    background: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 3px;
    width: 32px;
    height: 32px;
    padding: 6px;
    border-radius: var(--r-sm);
    color: var(--text-secondary);
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    flex-shrink: 0;
  }
  .z-tabs__hamburger:hover {
    background: var(--surface-raise-2);
    color: var(--text-primary);
    border-color: var(--border-subtle);
  }
  .z-tabs__hamburger-bar {
    width: 16px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
    transition: transform 0.2s, opacity 0.2s;
  }

  .z-tabs--rail .z-tabs__btn {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    justify-content: flex-start;
    border-bottom: none;
    border-radius: var(--r-sm);
    padding: var(--sp-2) var(--sp-3);
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    position: relative;
    color: var(--text-secondary);
    transition: background 0.15s, color 0.15s;
  }
  .z-tabs__btn-icon {
    font-size: 1.15rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    flex-shrink: 0;
  }
  .z-tabs__btn-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .z-tabs--rail .z-tabs__btn:hover { background: var(--surface-raise-2); color: var(--text-primary); }
  .z-tabs--rail .z-tabs__btn.is-active { background: var(--accent-soft); color: var(--accent-text); box-shadow: inset 0 0 0 1px var(--accent-line); }

  /* Collapsed rail state */
  .z-tabs--rail.is-collapsed .z-tabs__nav {
    padding: var(--sp-2) var(--sp-1);
    align-items: center;
  }
  .z-tabs--rail.is-collapsed .z-tabs__rail-header {
    justify-content: center;
    padding: var(--sp-1) 0 var(--sp-2);
    width: 100%;
  }
  .z-tabs--rail.is-collapsed .z-tabs__rail-title {
    display: none;
  }
  .z-tabs--rail.is-collapsed .z-tabs__btn {
    width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
    align-items: center;
    border-radius: var(--r-md);
  }
  .z-tabs--rail.is-collapsed .z-tabs__btn-label {
    display: none;
  }
  .z-tabs--rail.is-collapsed .z-tabs__btn-icon {
    margin: 0;
    font-size: 1.25rem;
  }

  @media (max-width: 899px) {
    .z-tabs--rail,
    .z-tabs--rail.is-collapsed { grid-template-columns: minmax(0, 1fr); }
    .z-tabs--rail .z-tabs__nav,
    .z-tabs--rail.is-collapsed .z-tabs__nav {
      flex-direction: row;
      overflow-x: auto;
      position: static;
      align-items: center;
      padding: var(--sp-2);
    }
    .z-tabs__rail-header {
      margin-bottom: 0;
      border-bottom: none;
      border-right: 1px solid var(--border-subtle);
      padding: 0 var(--sp-2) 0 0;
    }
    .z-tabs__rail-title { display: none; }
    .z-tabs--rail.is-collapsed .z-tabs__btn-label { display: inline; }
    .z-tabs--rail.is-collapsed .z-tabs__btn { width: auto; height: auto; padding: var(--sp-2) var(--sp-3); }
  }


  /* ══ table ════════════════════════════════════════════════════════════════════════════════
     Replaces .log-table (19 uses across 7 pages, defined only in features.php), .premium-table,
     .ledger-table, and a bare `table {}` rule in finances.php that leaked to every page. */
  .z-table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-1);
  }

  .z-table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }

  .z-table th {
    text-align: left;
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-muted);
    background: var(--surface-raise-1);
    border-bottom: 1px solid var(--border-default);
    white-space: nowrap;
  }
  .z-table td {
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: middle;
  }
  .z-table tbody tr:last-child td { border-bottom: none; }
  .z-table tbody tr:hover td { background: var(--surface-raise-1); }

  .z-table__num, .z-table th.z-table__num { text-align: right; font-variant-numeric: tabular-nums; }

  .z-table--dense th, .z-table--dense td { padding: var(--sp-2) var(--sp-3); }
  .z-table--zebra tbody tr:nth-child(even) td { background: var(--surface-raise-1); }
  .z-table--sticky-head th { position: sticky; top: 0; z-index: var(--z-raised); }


  /* ══ list ═════════════════════════════════════════════════════════════════════════════════
     The icon / title / subtitle / action row that nearly every page rebuilds by hand. */
  .z-list { display: flex; flex-direction: column; }

  .z-list__item {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) 0;
    min-height: 44px;
  }
  .z-list--divided .z-list__item + .z-list__item { border-top: 1px solid var(--border-subtle); }
  .z-list--hoverable .z-list__item { border-radius: var(--r-sm); padding-inline: var(--sp-2); margin-inline: calc(var(--sp-2) * -1); }
  .z-list--hoverable .z-list__item:hover { background: var(--surface-raise-1); }

  .z-list__lead  { flex: none; width: 1.6rem; text-align: center; font-size: 1.05rem; }
  .z-list__body  { flex: 1; min-width: 0; }
  .z-list__title { font-weight: var(--fw-medium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .z-list__sub   { font-size: var(--fs-xs); color: var(--text-muted); }
  .z-list__trail { margin-left: auto; flex: none; display: flex; align-items: center; gap: var(--sp-2); }


  /* ══ tile ═════════════════════════════════════════════════════════════════════════════════
     The inset sub-card every list page rebuilt by hand: a translucent block inside a .card,
     used for one goal, one bookmark, one saving pot. Nine pages declared their own copy under
     nine different names (.bk-card, .decision-card-item, .goal-card-main, .media-card-item,
     .routine-card-block, .save-goal-card, .trip-details-card, .wish-card-item), each with the
     same rgba(255,255,255,0.02) background that broke in light mode. */
  .z-tile {
    background: var(--surface-raise-1);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    padding: var(--sp-4);
    margin-bottom: var(--sp-3);
  }
  .z-tile:last-child { margin-bottom: 0; }

  /* The variant whose contents sit on one line with the actions pushed right. */
  .z-tile--row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3);
  }

  .z-tile--compact { padding: var(--sp-3); }

  /* ══ empty state ══════════════════════════════════════════════════════════════════════════
     Replaces ~110 inline "No X yet" paragraphs and the <tr><td colspan> variant. */
  .z-empty {
    display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
    padding: var(--sp-8) var(--sp-4);
    text-align: center;
    color: var(--text-muted);
  }
  .z-empty__icon  { font-size: 2rem; opacity: 0.8; }
  .z-empty__title { font-family: var(--font-display); font-weight: var(--fw-semi); color: var(--text-secondary); }
  .z-empty__text  { font-size: var(--fs-md); max-width: 34ch; }
  .z-empty > .btn { margin-top: var(--sp-2); }


  /* ══ layout helpers ═══════════════════════════════════════════════════════════════════════
     .z-split replaces nine byte-identical `350px 1fr` rules declared under nine different
     names (.bk-layout, .dec-layout, .goals-layout, .media-layout, .rout-layout, .save-layout,
     .track-layout, .travel-layout, .wish-layout), each with its own copy of the breakpoint. */
  .z-split {
    display: grid;
    grid-template-columns: minmax(0, var(--split-aside, 350px)) minmax(0, 1fr);
    gap: var(--sp-5);
    align-items: start;
  }
  .z-split--reverse { grid-template-columns: minmax(0, 1fr) minmax(0, var(--split-aside, 350px)); }

  .z-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--min, 220px), 1fr));
    gap: var(--sp-4);
  }
  .z-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .z-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .z-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  @media (max-width: 899px) {
    .z-split, .z-split--reverse { grid-template-columns: minmax(0, 1fr); }
    .z-grid--3, .z-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  @media (max-width: 639px) {
    .z-grid--2, .z-grid--3, .z-grid--4 { grid-template-columns: minmax(0, 1fr); }
  }


  /* A centred reading column. reviews.php and ambient.php each nested a second
     .app-container inside the real one to get this, which meant two elements claiming the
     same layout role. */
  .z-narrow { width: 100%; max-width: 900px; margin: 0 auto; }

  /* ══ toolbar, chip, section ═══════════════════════════════════════════════════════════════ */
  .z-toolbar {
    display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
    margin-bottom: var(--sp-4);
  }
  .z-toolbar__spacer { margin-left: auto; }

  .z-chip {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-full);
    background: var(--surface-raise-1);
    border: 1px solid var(--border-default);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    text-decoration: none;
  }
  a.z-chip:hover, .z-chip--selectable:hover { border-color: var(--accent-line); background: var(--accent-soft); text-decoration: none; }
  .z-chip.is-selected { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); }

  .z-section { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-4); }
  .z-section__title { font-size: var(--fs-lg); }
  .z-section__aside { margin-left: auto; display: flex; gap: var(--sp-2); }


  /* ══ segmented control ════════════════════════════════════════════════════════════════════
     Replaces memento.php's unique approach of swapping .btn and .btn-secondary classes. */
  .z-seg {
    display: inline-flex;
    padding: 3px;
    gap: 2px;
    background: var(--surface-raise-1);
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
  }
  .z-seg__opt {
    padding: var(--sp-2) var(--sp-3);
    border: none; background: none;
    border-radius: var(--r-sm);
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: var(--fs-sm);
    cursor: pointer;
  }
  .z-seg__opt.is-active { background: var(--accent-soft); color: var(--accent-text); }


  /* ══ switch ═══════════════════════════════════════════════════════════════════════════════
     The Windows toggle: a 44×20 pill, the accent when on. Markup:
       <span class="z-switch"><input type="checkbox" class="z-switch__input">
         <span class="z-switch__track"><span class="z-switch__thumb"></span></span></span> */
  .z-switch {
    position: relative;
    flex: none;
    display: inline-flex;
    align-items: center;
  }
  .z-switch__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: default;
  }
  .z-switch__track {
    display: block;
    width: 44px;
    height: 20px;
    padding: 3px;
    border: 2px solid var(--text-secondary);
    border-radius: var(--r-full);
    background: transparent;
    transition: background var(--dur-fast) var(--ease-decel), border-color var(--dur-fast) var(--ease-decel);
  }
  .z-switch__thumb {
    display: block;
    width: 10px;
    height: 10px;
    border-radius: var(--r-full);
    background: var(--text-secondary);
    transition: transform var(--dur) var(--ease-decel), background var(--dur-fast) var(--ease-decel);
  }
  .z-switch:hover .z-switch__track { border-color: var(--text-primary); }
  .z-switch:hover .z-switch__thumb { background: var(--text-primary); transform: scale(1.2); }
  .z-switch__input:checked ~ .z-switch__track { background: var(--accent); border-color: var(--accent); }
  .z-switch__input:checked ~ .z-switch__track .z-switch__thumb { background: var(--accent-contrast); transform: translateX(24px); }
  .z-switch__input:checked ~ .z-switch__track:hover .z-switch__thumb { transform: translateX(24px) scale(1.2); }
  .z-switch__input:focus-visible ~ .z-switch__track { box-shadow: var(--focus-ring); }
  .z-switch__input:disabled ~ .z-switch__track { opacity: 0.4; }

  /* ══ skeleton ═════════════════════════════════════════════════════════════════════════════ */
  .z-skel {
    background: linear-gradient(90deg, var(--surface-raise-1) 25%, var(--surface-raise-2) 50%, var(--surface-raise-1) 75%);
    background-size: 200% 100%;
    animation: z-shimmer 1.4s linear infinite;
    border-radius: var(--r-sm);
    height: 1em;
  }
  .z-skel--text { height: 0.8em; margin-block: 0.25em; }
  .z-skel--card { height: 96px; border-radius: var(--r-md); }
  @keyframes z-shimmer { to { background-position: -200% 0; } }


  /* ══ form extras ══════════════════════════════════════════════════════════════════════════ */
  .form-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-5); }
  .form-actions .btn { flex: 1; justify-content: center; }
  .form-hint  { font-size: var(--fs-xs); color: var(--text-muted); margin-top: var(--sp-1); }
  .form-error { font-size: var(--fs-xs); color: var(--danger-text); margin-top: var(--sp-1); }
  .form-row   { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-3); }

  textarea.form-control { resize: vertical; min-height: 72px; }
  select.form-control   { cursor: pointer; }

  .btn-ghost {
    background: none;
    border: 1px solid transparent;
    color: var(--text-muted);
  }
  .btn-ghost:hover { background: var(--surface-raise-2); color: var(--text-primary); box-shadow: none; }

  .btn:disabled, .btn[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }


  /* ══ compatibility ════════════════════════════════════════════════════════════════════════
     The pages still use the old names. Mapping them here means a page picks up the new look
     the moment its own <style> block is deleted, rather than needing markup edits first.
     These are removed once the last page has migrated. */

  .bar-container { display: flex; flex-direction: column; gap: var(--sp-2); }
  .bar-meta      { display: flex; justify-content: space-between; align-items: baseline; font-size: var(--fs-xs); color: var(--text-muted); }
  .bar-meta strong { color: var(--text-primary); font-variant-numeric: tabular-nums; }
  .bar-track {
    height: 8px;
    background: var(--surface-inset);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    overflow: hidden;
  }
  .bar-fill {
    height: 100%;
    border-radius: inherit;
    background: var(--accent-grad);
    transition: width var(--dur-slow) var(--ease-out);
  }
}

/*
 * Compatibility rules that must NOT sit in a layer.
 *
 * .status-badge is defined inside three page-level <style> blocks, which are unlayered and so
 * beat anything in @layer components. Matching that specificity is the only way these apply on
 * the pages that need them; they are deleted alongside those blocks.
 */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.2rem 0.55rem;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  line-height: 1.5;
  border-radius: var(--r-full);
  background: var(--surface-raise-2);
  color: var(--text-secondary);
  border: 1px solid var(--border-default);
  white-space: nowrap;
}
.status-badge.safe, .status-badge.success {
  background: var(--success-soft); color: var(--success-text); border-color: var(--success-line);
}
.status-badge.due, .status-badge.warning {
  background: var(--warning-soft); color: var(--warning-text); border-color: var(--warning-line);
}
.status-badge.overdue, .status-badge.danger {
  background: var(--danger-soft); color: var(--danger-text); border-color: var(--danger-line);
}
.status-badge.info {
  background: var(--info-soft); color: var(--info-text); border-color: var(--info-line);
}

@layer components {
    /* ── tags ─────────────────────────────────────────────────────────────────────────────
       A chip is a button, because in the editor it removes the tag and elsewhere it runs a
       search. Styled from --accent through color-mix() like every other tinted surface, so it
       follows a changed accent and works in both themes without a second declaration. */

    .tag-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 0.4rem;
        min-height: 2rem;
    }

    .tag-chip {
        display: inline-flex;
        align-items: center;
        gap: 0.35rem;
        padding: 0.2rem 0.55rem;
        border: 1px solid var(--accent-line);
        border-radius: var(--r-full);
        background: color-mix(in srgb, var(--accent) 12%, transparent);
        color: var(--text-primary);
        font: inherit;
        font-size: 0.78rem;
        line-height: 1.6;
        cursor: pointer;
    }

    .tag-chip:hover {
        background: color-mix(in srgb, var(--accent) 22%, transparent);
    }

    /* The tag button a module puts on a row. Quiet until hovered: it is an affordance, not a
       call to action, and a list of thirty rows should not carry thirty loud buttons. */
    .tag-btn {
        border: 1px solid transparent;
        border-radius: var(--r-sm);
        background: none;
        color: var(--text-muted);
        font-size: 0.85rem;
        line-height: 1;
        padding: 0.25rem 0.4rem;
        cursor: pointer;
        opacity: 0.55;
    }

    .tag-btn:hover,
    .tag-btn:focus-visible {
        opacity: 1;
        border-color: var(--accent-line);
        color: var(--text-primary);
    }


  /* ══ generated forms ══════════════════════════════════════════════════════════════════════
     The dialog zFormModal() builds in assets/js/ui.js, where a prompt() chain used to be.
     It reuses .modal-overlay, .glass-card, .form-group and .form-control, so only what is
     genuinely new is here: the two-column pairing, and the three bits of supporting text a
     prompt could never show -- an intro, a per-field hint, and an "optional" marker. */
  .z-form__title { margin-bottom: var(--sp-1); }

  .z-form__intro {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin-bottom: var(--sp-4);
  }

  .z-form__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--sp-3);
  }
  /* A field is full width unless it asked to be paired. */
  .z-form__grid > .form-group { grid-column: 1 / -1; }
  .z-form__grid > .form-group.z-form__half { grid-column: span 1; }

  .z-form__opt {
    color: var(--text-muted);
    font-weight: 400;
    font-size: var(--fs-xs);
  }

  .z-form__help {
    display: block;
    margin-top: var(--sp-1);
    color: var(--text-muted);
    font-size: var(--fs-xs);
  }

  .z-form__check {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    cursor: pointer;
  }

  .z-form__actions {
    display: flex;
    gap: var(--sp-2);
    margin-top: var(--sp-5);
  }

  /* The dialog is capped by .auth-container at 450px, so a paired row is already narrow.
     Below the small breakpoint it stops being worth it. */
  @media (max-width: 479px) {
    .z-form__grid > .form-group.z-form__half { grid-column: 1 / -1; }
  }

  /* ══ context menu ═════════════════════════════════════════════════════════════════════════
     The one right-click menu, built by zMenuOpen() in assets/js/ui.js. Every surface that
     wants a menu contributes items to it rather than shipping its own panel: the File
     Explorer used to carry a hand-written #files-context-menu of its own, which is the
     vocabulary this is lifted from.

     Above --z-palette so it can be raised over the Start menu and a window, and below
     --z-toast so a result still reports over the top of it. Positioned by script in viewport
     coordinates, hence `fixed`. */
  .z-menu {
      position: fixed;
      z-index: var(--z-context);
      min-width: 190px;
      max-width: min(320px, calc(100vw - 20px));
      display: flex;
      flex-direction: column;
      padding: var(--sp-1) 0;
      background: var(--surface-2);
      border: 1px solid var(--border-default);
      border-radius: var(--r-md);
      box-shadow: var(--shadow-3);
      -webkit-backdrop-filter: var(--glass-filter);
      backdrop-filter: var(--glass-filter);
      /* A menu is a pointer target, not prose: a drag that starts on it should not paint a
         selection across the labels. */
      user-select: none;
      animation: zMenuIn var(--dur-fast) var(--ease-out);
  }

  @keyframes zMenuIn {
      from { opacity: 0; transform: scale(0.96) translateY(-4px); }
      to   { opacity: 1; transform: none; }
  }

  /* Honours the system setting: the scale-in is decoration, and vestibular sensitivity is not
     a preference the rest of the app ignores either. */
  @media (prefers-reduced-motion: reduce) {
      .z-menu { animation: none; }
  }

  .z-menu__head {
      display: block;
      padding: var(--sp-2) 0.85rem var(--sp-1);
      font-size: var(--fs-2xs);
      font-weight: var(--fw-semi);
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: var(--tracking-wide);
      /* Long file names are the norm here, and a menu that grows to fit one stops being a
         menu. */
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
  }

  .z-menu__item {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
      width: 100%;
      padding: 0.5rem 0.85rem;
      font: inherit;
      font-size: var(--fs-sm);
      color: var(--text-primary);
      background: transparent;
      border: 0;
      text-align: left;
      text-decoration: none;
      cursor: pointer;
      transition: background var(--dur-fast), color var(--dur-fast);
  }

  /* Hover and keyboard focus are the same state deliberately: arrowing through the menu and
     running the mouse down it must look identical, or the caret appears to be in two places. */
  .z-menu__item:hover,
  .z-menu__item:focus {
      background: var(--surface-3);
      color: var(--text-primary);
      text-decoration: none;
      outline: none;
  }

  .z-menu__item[disabled] {
      color: var(--text-muted);
      cursor: default;
      opacity: 0.6;
  }
  .z-menu__item[disabled]:hover { background: transparent; }

  .z-menu__item--danger { color: var(--danger-text); }
  .z-menu__item--danger:hover,
  .z-menu__item--danger:focus { background: var(--danger-soft); color: var(--danger-text); }

  .z-menu__icon {
      flex: none;
      width: 1.25rem;
      font-size: 1rem;
      line-height: 1;
      display: inline-flex;
      align-items: center;
      justify-content: center;
  }

  .z-menu__label {
      flex: 1;
      font-weight: var(--fw-medium);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
  }

  /* The shortcut column: Alt+F4 beside Close, the way a desktop menu teaches its own
     accelerators. Never the only signal -- it is a reminder, not a control. */
  .z-menu__hint {
      flex: none;
      font-size: var(--fs-2xs);
      color: var(--text-muted);
  }

  /* A submenu parent: "Open with >", "New >". The chevron is the whole affordance, so it sits
     hard right and stays visible rather than appearing on hover -- a row that only reveals it
     has more behind it when you are already on it teaches nobody. */
  .z-menu__chev {
      flex: none;
      margin-left: auto;
      padding-left: var(--sp-2);
      font-size: var(--fs-sm);
      color: var(--text-muted);
  }

  /* The open branch stays lit while its panel is up, so two panels on screen still read as one
     path rather than as two menus. */
  .z-menu__item--sub[aria-expanded="true"] {
      background: var(--surface-2);
      color: var(--text-primary);
  }

  .z-menu__sep {
      height: 1px;
      margin: var(--sp-1) 0;
      background: var(--border-subtle);
  }
}

/* File picker (zFilePicker in ui.js). */
.z-picker { max-width: 560px; }
.z-picker__crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem; font-size: var(--fs-xs); margin-bottom: 0.5rem; }
.z-picker__crumbs button { border: 0; background: none; color: var(--accent); font: inherit; padding: 0.1rem 0.3rem; border-radius: var(--r-xs); cursor: pointer; }
.z-picker__crumbs button:hover { background: var(--surface-3); }
.z-picker__crumbs span { color: var(--text-muted); }
.z-picker__list { max-height: 50vh; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; border: 1px solid var(--border-subtle); border-radius: var(--r-sm); padding: 0.25rem; margin-bottom: 0.75rem; }
.z-picker__row { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.6rem; border: 0; border-radius: var(--r-xs); background: none; color: var(--text-primary); font: inherit; text-align: left; cursor: pointer; }
.z-picker__row:hover { background: var(--surface-3); }
.z-picker__row span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.z-picker__row small { color: var(--text-muted); }
.z-picker__note { color: var(--text-muted); font-size: var(--fs-sm); margin: 0.5rem; }

/* ══ celebrations (zCelebrate in ui.js) ═══════════════════════════════════════════════════
   A short burst over everything: confetti from the middle of the top edge, one critter across
   the viewport, and a pill that pops in near the bottom with what the moment paid. Nothing here
   takes the pointer. Reduced motion keeps the pill and drops the moving parts. */
.z-celebrate {
    position: fixed;
    inset: 0;
    z-index: var(--z-toast);
    pointer-events: none;
    overflow: hidden;
}
.z-celebrate.is-leaving .z-celebrate__pill { animation: z-pill-out 0.4s var(--ease-accel, ease-in) forwards; }
.z-confetti__bit {
    position: absolute;
    top: -12px;
    left: var(--x);
    width: 9px;
    height: 14px;
    border-radius: 2px;
    background: hsl(var(--h) 85% 60%);
    opacity: 0;
    animation: z-confetti-fall var(--d) var(--delay) cubic-bezier(0.2, 0.7, 0.4, 1) forwards;
}
.z-confetti__bit:nth-child(3n) { width: 12px; height: 12px; border-radius: 50%; }
.z-confetti__bit:nth-child(4n) { width: 6px; height: 16px; }
@keyframes z-confetti-fall {
    0%   { opacity: 1; transform: translate(0, 0) rotate(0deg); }
    100% { opacity: 0; transform: translate(var(--dx), 92vh) rotate(var(--r)); }
}
.z-celebrate__critter {
    position: absolute;
    bottom: 8vh;
    left: -80px;
    font-size: 56px;
    line-height: 1;
    filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.25));
    animation: z-critter-fly 1.9s cubic-bezier(0.3, 0.1, 0.3, 1) forwards;
}
@keyframes z-critter-fly {
    0%   { transform: translate(0, 0) rotate(-8deg) scale(0.8); opacity: 0; }
    12%  { opacity: 1; }
    50%  { transform: translate(55vw, -30vh) rotate(6deg) scale(1.15); }
    88%  { opacity: 1; }
    100% { transform: translate(115vw, -70vh) rotate(-4deg) scale(0.9); opacity: 0; }
}
.z-celebrate__pill {
    position: absolute;
    left: 50%;
    bottom: 14vh;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 1rem;
    border-radius: var(--r-full);
    background: var(--accent);
    color: var(--accent-contrast, #fff);
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-weight: 700;
    letter-spacing: 0.01em;
    box-shadow: var(--shadow-lg, 0 12px 30px rgba(0, 0, 0, 0.3));
    white-space: nowrap;
    animation: z-pill-pop 0.5s var(--ease-decel, ease-out) both;
}
.z-celebrate__pill i { font-style: normal; opacity: 0.6; }
@keyframes z-pill-pop {
    0%   { transform: translateX(-50%) translateY(20px) scale(0.6); opacity: 0; }
    60%  { transform: translateX(-50%) translateY(-6px) scale(1.08); opacity: 1; }
    100% { transform: translateX(-50%) translateY(0) scale(1); opacity: 1; }
}
@keyframes z-pill-out {
    to { transform: translateX(-50%) translateY(10px) scale(0.9); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .z-confetti__bit, .z-celebrate__critter { display: none; }
    .z-celebrate__pill { animation: none; }
}

/* ── The clock engine's ringing overlay ──────────────────────────────────────────────────────
   assets/js/clock-engine.js runs on every page, so the alert it raises needs styling that is
   available on every page too. The Clock module has its own richer overlay in its own markup;
   this is the one that appears everywhere else, and it deliberately sits above --z-modal so an
   alarm is never stuck behind whatever dialog happened to be open when it went off. */
/* An explicit `display` beats the user-agent's `[hidden] { display: none }`, so hiding either
   of these with el.hidden would otherwise do nothing at all: the overlay's Dismiss would never
   close it, and the Snooze button would stay up on a timer, which has nothing to snooze. Both
   set display in a class rule, so the attribute selector has to say so too. This is the same
   arrangement .ritual-net-pill and .z-keys already use in shell.css. */
.rc-ring-scrim[hidden] { display: none; }
.btn[hidden] { display: none; }

/* .btn-lg was used by the Clock module's ringing overlay (assets/js/clock-engine.js and
   app/modules/clock/page.php) and defined nowhere, so its Dismiss and Snooze buttons rendered
   at the ordinary size. A page's single most important action should be able to say so. */
.btn-lg {
  padding: 0.7rem 1.5rem;
  font-size: var(--fs-base);
  font-weight: var(--fw-semi);
  border-radius: var(--r-lg);
}
.rc-ring-scrim {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-palette) + 10);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    background: var(--surface-scrim);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}
.rc-ring-box {
    width: min(30rem, 100%);
    padding: var(--sp-8) var(--sp-6);
    text-align: center;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-1);
    box-shadow: 0 1.5rem 4rem rgb(0 0 0 / 45%);
}
.rc-ring-pulse {
    width: 5.5rem;
    height: 5.5rem;
    margin: 0 auto var(--sp-4);
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    background: var(--accent-soft);
    box-shadow: 0 0 0 0 var(--accent-glow);
    animation: rc-ring-pulse 1.6s ease-out infinite;
}
.rc-ring-icon { font-size: 2.75rem; line-height: 1; }
.rc-ring-title {
    margin: 0 0 var(--sp-2);
    font-family: var(--font-display);
    font-size: 1.5rem;
    color: var(--text-primary);
}
/* The time is the one thing that has to read from across a room, so it gets the display face
   and the light weight the rest of the clock uses for large numerals. */
.rc-ring-time {
    margin: 0;
    font-family: var(--font-display);
    font-size: 3rem;
    font-weight: var(--fw-light);
    font-variant-numeric: tabular-nums;
    color: var(--accent);
}
.rc-ring-sub { margin: var(--sp-2) 0 var(--sp-6); color: var(--text-muted); }
.rc-ring-actions { display: flex; gap: var(--sp-3); justify-content: center; flex-wrap: wrap; }
.rc-ring-actions .btn { min-width: 9rem; }

@keyframes rc-ring-pulse {
    0%   { box-shadow: 0 0 0 0 var(--accent-glow); }
    70%  { box-shadow: 0 0 0 1.75rem transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
    .rc-ring-pulse { animation: none; }
}


/* ══ the z-modal dialog shell ═════════════════════════════════════════════════════════════════

   A dialog built in JS, for the case zFormModal cannot serve: a list rather than a form.
   The File Explorer had this first and kept it in files.css, which meant Mail and Team Sites
   got the markup and none of the styling -- a backdrop that is not fixed is a block of text
   at the bottom of the page. It belongs here with the rest of the shared vocabulary.
   ════════════════════════════════════════════════════════════════════════════════════════ */
.z-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-scrim);
    padding: var(--sp-4);
}
.z-modal {
    width: 100%;
    max-width: 440px;
    max-height: calc(100vh - 2 * var(--sp-4));
    overflow: auto;
    padding: var(--sp-5);
    background: var(--surface-2);
    border: 1px solid var(--border-strong);
    border-radius: 0;
    box-shadow: var(--shadow-4);
}
.z-modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.z-modal-header h3 { font-size: var(--fs-xl); font-weight: var(--fw-semilight); }
.z-modal-actions { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-5); }

/* ════════════════════════════════════════════════════════════════════════════════════════
   AI affordances
   ────────────────────────────────────────────────────────────────────────────────────────
   The RitualAI master switch (Settings → AI) puts data-ai="0" on <html>. Mark any AI entry
   point anywhere in the suite with .ai-only and it goes with the switch — one rule instead of
   a condition in every module.

   This is the cosmetic half of the gate only. The switch is enforced for real in
   ritual_is_visible(), in ritual_render() and in app/api/ai.php; hiding a button has never
   been the same thing as closing a door.
   ════════════════════════════════════════════════════════════════════════════════════════ */
[data-ai="0"] .ai-only { display: none !important; }

/* Self-Care's day states. A low-battery day hides anything that counts a streak -- point at it
   with .lb-hide and it goes with the switch. The bars sit under the admin broadcast. */
[data-lowbattery="1"] .lb-hide { display: none !important; }
.sc-bar {
  padding: .45rem 1rem;
  font-size: var(--fs-sm);
  text-align: center;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-default);
}
.sc-bar--lowbattery { background: color-mix(in srgb, #34d399 14%, transparent); }
.sc-bar--downtime   { background: color-mix(in srgb, #a78bfa 16%, transparent); }
.sc-bar a { color: inherit; font-weight: 600; margin-left: .5rem; text-decoration: underline; text-underline-offset: 2px; }
.sc-bar__muted { color: var(--text-muted); }
