/* perch overlay — island anatomy and activity views, matched to the Figma
   source of truth (Dynamic Perch). The pill + flares are ONE svg path
   (#shell, driven by perch.js) so notch and ears are physically fused.
   Motion is spring-driven from perch.js, only while transitioning. */

@import url("widgets.css");

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  width: 100%; height: 100%; background: transparent; overflow: hidden;
  user-select: none; font-family: var(--font);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
#stage { width: 100%; height: 100%; display: flex; justify-content: center; align-items: flex-start;
  transition: opacity 0.4s var(--ease-out); }
/* S3 "dim" game rule: the whole ensemble fades while the game runs */
#stage.dimmed { opacity: 0.35; }

#island {
  position: relative;
  color: var(--text-primary);
  width: 215px; height: 42px;
  /* box-shadow follows the pill only (flares are subtle); radius kept in
     sync with the shell path by perch.js for #clip + shadow shape */
  border-radius: 0 0 21px 21px;
  box-shadow: var(--shadow-island);
  will-change: width, height, transform; transform-origin: top center;
}
#shell { position: absolute; top: 0; left: 0; overflow: visible; pointer-events: none; }
#shell-base { fill: var(--island); } /* themed (S5): CSS beats the svg fill attribute */

/* Inner clip: pill content. */
#clip {
  position: absolute; inset: 0; border-radius: inherit; overflow: hidden;
  display: flex; align-items: center;
}
/* Glass glint: a hairline of light along the pill's bottom curve. */
#clip::after { content: ""; position: absolute; left: 12%; right: 12%; bottom: 0; height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.14), transparent); }

/* ---- expanded page (S0 redesign, DEC-0057: 800×160 = 43px tab strip + content; equal-width slots) ---- */
#expanded { position: absolute; inset: 0; border-radius: inherit; overflow: hidden;
  display: flex; flex-direction: column; padding: 0;
  opacity: 0; pointer-events: none; transition: opacity 0.18s var(--ease-out); }
#island.is-expanded #expanded { opacity: 1; pointer-events: auto; }
/* 23px = the page's content inset — tabs align with the media art's left
   edge (owner, M7 polish; the player loses its inner left padding in slots) */
.page-bar { flex: none; height: 45px; display: flex; align-items: center; gap: 8px; padding: 0 16px; } /* DEC-0147: Figma bar 45, tab chip 16 from edge */
/* S0 (DEC-0057): icon + NAME pill. Active = translucent fill + full-white
   text; inactive = transparent + dimmed; hover brightens. Auto width. */
.tab { display: flex; align-items: center; gap: 5px; padding: 6px 9px; border-radius: 15px; /* DEC-0147: Figma chip 70×25 r15 */
  color: var(--text-secondary); background: transparent;
  cursor: pointer; transition: background 0.15s var(--ease-out), color 0.15s var(--ease-out), transform 0.1s var(--ease-out); }
.tab-name { font-size: 13px; font-weight: 600; white-space: nowrap; line-height: 13px; }
/* Overflowing strip: non-active tabs shrink to icons; the active keeps its
   label (Session 12 — labels must never clip off the panel edge). */
.page-bar.collapsed .tab:not(.active) .tab-name { display: none; }
.page-bar.collapsed .tab:not(.active) { padding: 5px 7px; }
.tab:hover:not(.active) { background: rgba(217, 217, 217, 0.1); color: var(--text-primary); }
.tab:active { transform: scale(0.96); }
.tab.active { background: rgba(217, 217, 217, 0.2); color: var(--text-primary); }
.page { flex: 1; display: flex; align-items: stretch; min-height: 0; padding: 0 15px 15px; gap: 0; } /* DEC-0147: 15+370+15 per half */
.slot { position: relative; display: flex; align-items: center; min-width: 0; }
/* Divider spacing is the SLOTS' job, not each widget's (owner refinement,
   Session 7): every widget gets the same 16px breathing room on its
   divider-facing edge, whichever slot it sits in. */
/* S0 (DEC-0057): equal-width slots (~360px each at the 800px envelope) —
   the primary/secondary width split is retired; only the FORMS stay
   slot-bound (DEC-0048). */
#r-page-left { flex: 1 1 0; min-width: 0; padding-right: 15px; }
#r-page-right { flex: 1 1 0; min-width: 0; padding-left: 15px; }
/* DEC-0147 amended (owner, S24 addendum): 370×100 stays the widget contract
   but it is enforced by DISCIPLINE, not clipping — overflow:hidden here was
   amputating hover glows on the media controls. Widgets must size their
   CONTENT inside the region; only ephemera (hover halos, focus rings) may
   paint past it. */
#r-page-left, #r-page-right { overflow: visible; }
#r-page-left .activity, #r-page-right .activity { max-height: 100px; }
/* Exactly 100px at the 1440p reference (owner spec) — was stretch (≈102). */
.page-div { flex: none; width: 1px; height: 100px; align-self: center; background: var(--hairline); }

/* Regions: activities mount here. position:relative so a leaving node can be
   frozen absolutely within its region (see unmount). */
.region { position: relative; display: flex; align-items: center; min-width: 0; height: 100%; }
#r-left { flex: none; }
#r-fill { flex: 1; justify-content: center; }
/* S25: the in-pill secondary seat — degraded followers (media art icon,
   discord faces, S1 chips) sit INSIDE the pill at its right edge; perch.js
   widens the realized envelope to hold them (gap/inset mirrored in
   RIGHT_GAP/RIGHT_INSET — keep in sync). */
#r-right { flex: none; gap: 8px; }
#r-right:not(:empty) { padding-right: 10px; } /* DEC-0147: chip inset 10 */
#r-right .activity { width: auto; height: auto; }

/* ---- activity entrance/exit: fade + de-blur + settle, children staggered ---- */
.activity { display: flex; align-items: center; min-width: 0; width: 100%; height: 100%;
  opacity: 0; transform: scale(0.97); filter: blur(7px);
  transition: opacity var(--enter-dur) var(--ease-out) 0.04s,
              transform var(--enter-dur) var(--ease-out),
              filter var(--enter-dur) var(--ease-out) 0.04s; }
.activity.show { opacity: 1; transform: scale(1); filter: blur(0); }
.activity.hide { opacity: 0; transform: scale(0.955); filter: blur(5px);
  transition: opacity var(--exit-dur) var(--ease-in),
              transform var(--exit-dur) var(--ease-in),
              filter var(--exit-dur) var(--ease-in); }

/* FLIP-mounted nodes (perch.js): the glide IS the entrance — no fade, no
   stagger, children must not run entrance animations that would fight the
   per-frame inverted transform. */
.activity.noenter, .activity.noenter > * { transition: none !important; animation: none !important; }

/* Page swaps (tab clicks): the outgoing and incoming widgets slide as ONE
   directional movement — no scale/blur pop, no per-child stagger, and BOTH
   halves share the same duration/curve with no opacity delay (the default
   130 ms exit vs 260 ms + 40 ms-delayed entry left a half-invisible dip). */
.activity.swap-in-next, .activity.swap-in-prev {
  filter: none;
  transition: opacity var(--enter-dur) var(--ease-out), transform var(--enter-dur) var(--ease-out); }
.activity.swap-in-next { transform: translateX(20px); }
.activity.swap-in-prev { transform: translateX(-20px); }
.activity.swap-in-next.show, .activity.swap-in-prev.show { transform: translateX(0); }
.activity.swap-in-next > *, .activity.swap-in-prev > * { animation: none !important; }
.activity.hide.swap-out-next, .activity.hide.swap-out-prev {
  filter: none;
  transition: opacity var(--enter-dur) var(--ease-out), transform var(--enter-dur) var(--ease-out); }
.activity.hide.swap-out-next { transform: translateX(-20px); }
.activity.hide.swap-out-prev { transform: translateX(20px); }

@keyframes rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: translateY(0); } }

/* S14 move 2 (MediaNotchDemo): pill-region entrances POP — children scale
   .55→1 with the demo's back-out overshoot (.34,1.55,.5,1 @ .52s), second
   group +0.07s. Scoped to the collapsed pill's regions; the expanded panel
   keeps `rise`. .noenter still kills everything (rule above). */
@keyframes pop { from { opacity: 0; transform: scale(0.55); } to { opacity: 1; transform: scale(1); } }
#r-left .activity.show > *, #r-fill .activity.show > *, #r-right .activity.show > * {
  animation: pop 0.52s cubic-bezier(0.34, 1.55, 0.5, 1) backwards; }
#r-left .activity.show > :nth-child(1), #r-fill .activity.show > :nth-child(1), #r-right .activity.show > :nth-child(1) {
  animation-delay: 0s; }
#r-left .activity.show > :nth-child(n+2), #r-fill .activity.show > :nth-child(n+2), #r-right .activity.show > :nth-child(n+2) {
  animation-delay: 0.07s; }

.activity.show > * { animation: rise 0.38s var(--ease-spring) backwards; }
.activity.show > :nth-child(1) { animation-delay: 0.02s; }
.activity.show > :nth-child(2) { animation-delay: calc(0.02s + var(--stagger)); }
.activity.show > :nth-child(3) { animation-delay: calc(0.02s + var(--stagger) * 2); }
.activity.show > :nth-child(4) { animation-delay: calc(0.02s + var(--stagger) * 3); }

/* ---- overlay-only widget rules (the shared widget styles live in
        widgets.css — imported below — so the settings preview renders the
        REAL widgets, owner Session 9) ---- */
/* @import CASCADE REPAIR (owner caught it live, Session 9): widgets.css
   sorts BEFORE this sheet's own rules, so `.activity { align-items:center }`
   — equal specificity, later — silently beat the widget classes' overrides
   once they moved out (the voice card rendered fully centered). The
   .activity.X compounds outrank both sheets. Any future root-level widget
   rule that overrides an .activity property needs a row here. */
.activity.mod-widget:has(.m-field) { user-select: text; } /* field trees must select/edit (body disables it) */
.activity.notepad-widget { align-items: stretch; } /* the sheet fills; beats .activity's center (specificity) */
.activity.stats-widget { align-items: stretch; }
.activity.pomo-widget { align-items: center; }
.activity.greeting-panel { align-items: stretch; }
/* (discord card align-stretch + facepile rise animation retired — the card is
   a mod-widget display tree now; its root column owns alignment.) */

/* ---- media compact (DEC-0147 Figma-exact): SOLO pill = art left +
        visualizer right, NO text ("Notch single compact view"); with a
        seated secondary = art + title/artist ("Dual Compact Primary") ---- */
.media-compact { padding: 0 12px 0 10px; justify-content: flex-start; gap: 4px; }
.media-compact .mc-text { flex: 1; display: none; flex-direction: column; justify-content: center;
  min-width: 0; gap: 2px; }
.media-compact .mc-title { font-size: 13px; font-weight: 700; color: var(--text-primary); line-height: 16px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* 16 not 13: descenders (g/y) were clipped by the tight box under #clip's overflow:hidden (owner) */
.media-compact .mc-artist { font-size: 10px; font-weight: 700; color: var(--text-half); line-height: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-compact .art { width: 25px; height: 25px; border-radius: 7px; box-shadow: var(--rim-strong); }
.media-compact.paused .art { opacity: 0.6; }
.media-compact .mc-viz { flex: none; margin-left: auto; width: 24px; height: 20px;
  display: flex; align-items: center; gap: 1px; }
.media-compact .mc-viz span { flex: none; width: 4px; height: 20px; border-radius: 2px;
  background: linear-gradient(180deg, var(--viz-hi, #e5e18f), var(--viz-lo, #953f2f));
  transform: scaleY(0.2); transform-origin: center; } /* DEC-0147 owner: bars wear the art's palette */
/* a seated secondary flips the pill to its dual-compact form */
#clip:has(#r-right > *) .media-compact .mc-text { display: flex; }
#clip:has(#r-right > *) .media-compact .mc-viz { display: none; }
#clip:has(#r-right > *) .media-compact .art { border-radius: 5px; }

/* degraded to the in-pill right seat (S25): mini album art, same rounded
   square family as the pill faces */
.mini-art { width: 25px; height: 25px; border-radius: 7px; flex: none;
  background: linear-gradient(150deg, #262726, #111); background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-tertiary); box-shadow: var(--rim-strong), 0 3px 9px rgba(0, 0, 0, 0.45); }
.mini-art svg.note { width: 46%; height: 46%; }

/* ---- transients (DEC-0147 Figma-exact: pill 330×42, icon 20 at (22,13),
        label 700/15 at 55, track 120×8 white-20% right-inset 18) ---- */
/* .t-label stays: the CORE voice pills (exempt chrome) compose it */
.t-label { flex: none; font-size: 15px; font-weight: 700; color: var(--text-primary); }

/* ---- voice (DEC-0147 Figma-exact): mic 15×20 at (20,11), "Listening"
        700/15 at 43, green-gradient bars right inset 21; Thinking =
        centered gradient wordmark, bars hidden ---- */
.vc-listen { padding: 0 21px 0 20px; gap: 8px; justify-content: flex-start; color: var(--text-primary); }
.vc-listen > svg { flex: none; width: 16px; height: 20px; color: var(--live); }
.vc-listen .t-label { font-size: 15px; font-weight: 700; }
.vc-listen .vc-bars { flex: 1; min-width: 0; height: 25px; margin-left: auto;
  display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
.vc-listen .vc-bars span { flex: none; width: 3px; height: 25px; border-radius: 2px;
  background: linear-gradient(180deg, #a6fb89, #faffd6 50%, #a6fb89);
  transform: scaleY(0.14); transform-origin: center; }
.vc-listen.thinking { justify-content: center; padding: 0 20px; }
.vc-listen.thinking > svg { display: none; }
.vc-listen.thinking .vc-bars { display: none; }
/* The gradient MOVES (owner, 2026-07-14): a doubled-back sweep slides across
   the wordmark while the pill lives (seconds — no idle cost). */
.vc-listen.thinking .t-label { background: linear-gradient(90deg, #ff8fb0, #c57cff 25%, rgba(124, 140, 255, 0.9) 50%, #58c6ff 75%, #c57cff 88%, #ff8fb0);
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent; }
/* (the sweep is rAF-driven in widgets.js — a CSS animation stalls when the
   CEF pump idles; see the clock gauge precedent) */
/* Shazam (S-shazam): the "Identifying song" pill — twin visualizers flank a
   centered label (screenshot 1). Both react to the mic (rAF, level-scaled). */
/* Bars flank the label as fixed clusters (not flex:1 — that stretched them
   across empty space, owner). Centered group, tight gaps. */
.vc-identify { padding: 0 14px; gap: 10px; justify-content: center; color: var(--text-primary); }
.vc-identify .vi-label { flex: none; font-size: 15px; font-weight: 700; white-space: nowrap; }
.vc-identify .vi-bars { flex: none; width: 78px; height: 22px; display: flex; align-items: center; gap: 3px; }
.vc-identify .vi-l { justify-content: flex-end; }
.vc-identify .vi-r { justify-content: flex-start; }
.vc-identify .vi-bars span { flex: none; width: 3px; height: 22px; border-radius: 2px;
  background: linear-gradient(180deg, #7ec8ff, #cdeaff 50%, #7ec8ff);
  transform: scaleY(0.08); transform-origin: center; } /* Figma: blue identify wave */
/* The song result IS the media compact widget — force its text visible
   (media hides .mc-text outside the dual context; here it's always shown). */
.song-result .mc-text { display: flex !important; }
.song-result .mc-viz { display: none; }
.vc-reply { padding: 0 17px 0 20px; gap: 9px; justify-content: flex-start; }
.vc-reply .vc-spark { flex: none; width: 21px; height: 18px; object-fit: contain; }
.vc-reply.err .vc-spark { filter: drop-shadow(0 0 4px rgba(255, 56, 60, 0.9)); }
.vc-reply .vc-text { min-width: 0; font-size: 13px; font-weight: 700; color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* long replies (Session 21): the reply wraps ≤3 lines
   (the envelope grew to match in sizeFor — 17px per extra line) */
.vc-reply.long .vc-text { white-space: normal; display: -webkit-box;
  -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-height: 17px; }

/* ---- notify: sender avatar, name, text, timestamp ---- */
.notify-msg { padding: 0 14px 0 9px; gap: 5px; justify-content: flex-start; } /* DEC-0147: avatar 24 at (9,9), Now inset 14 */
.notify-msg .n-avatar { flex: none; width: 24px; height: 24px; border-radius: 50%; position: relative;
  background: linear-gradient(150deg, hsl(265 60% 62%), hsl(265 58% 44%));
  box-shadow: var(--rim-strong);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 11px; font-weight: 700; }
.notify-msg .n-avatar.has-img { background-size: cover; background-position: center; color: transparent; }
.notify-msg .n-avatar::after { content: ""; position: absolute; right: -1px; bottom: -1px;
  width: 9px; height: 9px; border-radius: 50%; background: var(--live); border: 2px solid var(--island); }
.notify-msg .n-body { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 5px; }
.notify-msg .n-from { flex: none; font-size: 15px; font-weight: 700; color: var(--text-primary); }
.notify-msg .n-text { min-width: 0; font-size: 13px; font-weight: 700; color: var(--text-half);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notify-msg .n-when { flex: none; }

/* steam activation notice (S6/M10): notify-shaped, Steam-blue avatar, no
   live badge (it's not a message) */
.steam-notice .n-avatar { background: linear-gradient(150deg, hsl(207 60% 42%), hsl(215 65% 22%)); }
.steam-notice .n-avatar::after { display: none; }
.steam-notice .n-avatar svg { width: 15px; height: 15px; }

/* (the discord chip / right-seat faces / incoming-call ring / notification
   inbox are pure display trees now — M-mod.2, DEC-0167. The shared `.face`
   avatar, `.discord-call`/`.discord-ring`/`.dr-*`/`.dc-*` chips, the
   `#clip/#r-right .facepile` seats, `.inbox-widget`/`.ib-*` rows and the
   `.discord-page` wrapper all retired; the package rebuilds them from
   frame/image/text/list primitives, and the provider ships view-ready faces.
   Motion (the ring halo/nod, face entrance pop) is gate-invisible and lives
   as feel-pass polish, not shipped in the first tree pass.) */

/* S1 static composition (S25 update — was an outside-right mini-pill): the
   non-leading ambient pill widget (date/weather) joins the pill's right seat
   as a small in-pill chip. */
/* DEC-0147 context forms (the Figma Default Widgets ruleset):
   single compact = the widget's two-sided form; DUAL (a secondary is
   seated) = left-grouped condensed form; NON-PRIMARY (the right seat) =
   the trailing element alone, full 17px type. */
/* ---- startup greeting (DEC-0147 Figma-exact): 700×140 = 45px header bar +
   95px body holding a 670×80 r25 white-15% recessed panel at (15,45) ---- */
.greeting-panel { flex-direction: column; align-items: stretch; padding: 0 15px 15px; gap: 0; }
.greeting-panel .g-head { display: flex; align-items: center; gap: 5px; min-width: 0; flex: none; height: 45px; }
.greeting-panel .g-dot { flex: none; width: 25px; height: 25px; border-radius: 50%; background: var(--live); }
.greeting-panel .g-dot.has-img { background-color: transparent; background-size: cover; background-position: center; }
/* DEC-0148: the dot wears the Steam avatar (or the OS account picture) */
.greeting-panel .g-hello { font-size: 13px; font-weight: 600; color: var(--text-primary); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.greeting-panel .g-hello b { font-weight: 600; }
.greeting-panel .g-city { margin-left: auto; font-size: 13px; font-weight: 700; color: var(--text-primary);
  white-space: nowrap; }
.greeting-panel .g-city:empty { display: none; } /* offline: the logo hugs the right edge */
.greeting-panel .g-logo { flex: none; height: 21px; width: auto; }
.greeting-panel .g-city:not(:empty) + .g-logo { margin-left: 7px; }
.greeting-panel .g-well { flex: none; height: 80px; background: rgba(255, 255, 255, 0.15); border-radius: 25px;
  display: flex; align-items: center; justify-content: space-between; padding: 6px 29px; }
/* DEC-0147/owner: Figma cells sit at 29px insets with EVEN 60px gaps —
   space-evenly crowded them toward the center */
.g-day { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.g-day .g-dn { font-size: 13px; font-weight: 700; color: var(--text-primary); height: 16px; line-height: 16px; }
.g-day .g-dt { font-size: 13px; font-weight: 700; color: var(--text-primary); height: 16px; line-height: 16px; }
.g-day svg { width: 32px; height: 19px; }
.g-off { display: flex; align-items: center; gap: 0; width: 100%; padding: 0 19px; min-width: 0; }
.g-off .g-clock { flex: none; font-size: 40px; font-weight: 700; color: var(--text-primary);
  font-variant-numeric: tabular-nums; }
.g-off .g-date { flex: none; font-size: 15px; font-weight: 700; color: var(--text-half); margin-left: 5px;
  align-self: flex-end; padding-bottom: 8px; }
.g-off .g-note { margin-left: auto; display: flex; align-items: center; gap: 11px; min-width: 0;
  color: var(--text-half); font-size: 13px; font-weight: 700; line-height: 15.7px; max-width: 56%;
  border-left: 1px solid rgba(217, 217, 217, 0.2); padding-left: 20px; align-self: stretch; }
.g-off .g-note svg { flex: none; opacity: 0.5; }

/* Primary-only page (DEC-0045): the panel collapses to the left widget. */
#island.is-solo .page-div, #island.is-solo #r-page-right { display: none; }

/* Solo page (owner fix): the player's inner right padding is divider spacing
   in the duo layout; alone it read as off-center (23px left vs 43px right).
   Symmetric insets center the primary in the collapsed panel. */
#island.is-solo #r-page-left { padding-right: 0; } /* symmetric 23/23 around the lone primary */

/* Phosphor page icons (DEC-0049) */
.tab .ph { font-size: 14px; }

/* mod notch context forms (Wave A — the general DEC-0147 rules for packages).
   justify-content needs !important: trees declare `justify` as an INLINE style
   (modBuildNode), which beats any plain rule — the date pill's space-between
   survived into the dual context and shoved the day number against the
   secondary chip (owner, 2026-07-15; sebotan's dual SVG groups "Thu 2" left). */
#clip:has(#r-right > *) #r-fill .mod-widget.m-notch > * { justify-content: flex-start !important; gap: 8px; }
/* gap stays SOFT: a tree that declares its own gap (inline style) keeps it in
   the dual form (weather's 13px); undeclared gaps fall back to the 8px here. */
#clip:has(#r-right > *) #r-fill .mod-widget.m-notch .m-show-dual { display: flex !important; }
#clip:has(#r-right > *) #r-fill .mod-widget.m-notch .m-show-single { display: none !important; }
#r-right .mod-widget.m-notch { padding: 0; }
/* (the slot-form show tokens moved to widgets.css — the settings preview
   renders the same trees and needs the same context rules) */
