/* =========================================================
   Desktop shell (OS mode)

   Every rule is scoped under html[data-os="on"], which only the window
   manager sets, and only above the desktop breakpoint. That makes it
   structurally impossible for CSS and JS to disagree about which mode is
   active: below the breakpoint this file matches nothing and the page is
   exactly the linear site.
   ========================================================= */

/* The one exception to the data-os="on" scoping above, and it has to be.
   The window manager is a module, so it cannot run before first paint; the
   inline head script sets data-os-pending synchronously instead. These rules
   hide the linear page and paint the wallpaper at first paint, so the desktop
   is the only thing a visitor ever sees. The head script also removes the
   attribute after a timeout, so a failed boot falls back to the linear site
   rather than a blank screen. */
html[data-os-pending] main,
html[data-os-pending] .site-header,
html[data-os-pending] .skip { display: none; }
html[data-os-pending],
html[data-os-pending] body {
  height: 100%;
  overflow: hidden;
  /* Same field as .desktop, so the wallpaper is already there when the shell
     mounts and there is no second transition. */
  background:
    radial-gradient(120% 90% at 18% 8%,
      color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 60%),
    radial-gradient(100% 80% at 88% 96%,
      color-mix(in srgb, var(--accent) 9%, transparent) 0%, transparent 55%),
    var(--bg);
}

html[data-os="on"],
html[data-os="on"] body {
  height: 100%;
  overflow: hidden;              /* the desktop does not scroll; windows do */
  overscroll-behavior: none;
  scroll-behavior: auto;
}

/* The linear page is still in the DOM (it is the mobile, no-JS and SEO
   payload); OS mode just hides it. */
html[data-os="on"] main.linear,
html[data-os="on"] .site-header,
html[data-os="on"] .skip { display: none; }

/* ---------- Desktop / wallpaper ---------- */
.desktop {
  position: fixed; inset: 0;
  z-index: var(--z-wallpaper);
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 18% 8%,
      color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 60%),
    radial-gradient(100% 80% at 88% 96%,
      color-mix(in srgb, var(--accent) 9%, transparent) 0%, transparent 55%),
    var(--bg);
}
/* Very fine grain so the large flat field does not band on wide displays. */
.desktop::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  opacity: .035;
  background-image: radial-gradient(currentColor .5px, transparent .5px);
  background-size: 3px 3px;
  color: var(--ink);
}

/* ---------- Window ---------- */
.win {
  position: fixed; top: 0; left: 0;          /* geometry lives in transform */
  display: flex; flex-direction: column;
  background: var(--card);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-win);
  overflow: hidden;
  contain: layout paint;
  transition: box-shadow .25s var(--ease);
}
.win.is-focused { box-shadow: var(--shadow-win-focus); }
/* A window that genuinely takes keyboard focus still says so, but the shared
   [tabindex] ring in main.css draws a 3px-radius box around a 10px-radius
   window, which reads as a rendering fault rather than an affordance. */
.win:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-md);
}
.win[hidden] { display: none; }

/* Title bar */
.win-bar {
  position: relative;
  flex: none;
  height: 38px;
  display: flex; align-items: center;
  padding-inline: var(--sp-3);
  gap: var(--sp-3);
  background: var(--surface-chrome);
  -webkit-backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  border-bottom: 1px solid var(--hairline);
  box-shadow: inset 0 1px 0 var(--hairline-hi);
  cursor: grab;
  touch-action: none;                         /* or touch drag scrolls instead */
  user-select: none;
}
.win-bar:active { cursor: grabbing; }
.win-title {
  position: absolute; left: 50%; transform: translateX(-50%);
  max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--sans);
  font-size: var(--fs-ui-md); font-weight: 600; letter-spacing: 0;
  color: var(--ink);
  transition: opacity .25s var(--ease);
  pointer-events: none;
}
.win:not(.is-focused) .win-title { opacity: .5; }

/* Traffic lights */
.win-lights { display: flex; align-items: center; gap: var(--sp-2); z-index: 1; }
.win-light {
  width: 12px; height: 12px; padding: 0;
  border-radius: 50%; border: none; cursor: pointer;
  background: var(--line);
  display: grid; place-items: center;
  transition: background .2s var(--ease);
}
.win-light svg { width: 8px; height: 8px; opacity: 0; transition: opacity .15s var(--ease); }
.win-lights:hover .win-light svg,
.win-light:focus-visible svg { opacity: .55; }
.win.is-focused .win-close { background: #FF5F57; }
.win.is-focused .win-min   { background: #FEBC2E; }
.win.is-focused .win-zoom  { background: #28C840; }
.win-close:hover { background: #FF5F57; }
.win-min:hover   { background: #FEBC2E; }
.win-zoom:hover  { background: #28C840; }

/* Body: opaque on purpose. Real macOS does not vibrancy-blur content, and
   this site is long-form reading. */
.win-body {
  flex: 1; min-height: 0;
  overflow: auto; overscroll-behavior: contain;
  background: var(--bg);
  container-type: inline-size;

  /* Type scales to the WINDOW, not the viewport. Without this every case
     study renders at hero scale inside a 900px window, because its clamps
     resolve vw against the viewport. */
  --gutter: clamp(1.1rem, 4cqi, 2.25rem);
}
.win-body .case-title      { font-size: clamp(1.9rem, 6.2cqi, 3rem); }
.win-body .case-deck       { font-size: clamp(1rem, 2cqi, 1.2rem); }
.win-body .case-section-title,
.win-body .hmw             { font-size: clamp(1.35rem, 3.6cqi, 1.95rem); }
.win-body .project-title   { font-size: clamp(1.25rem, 3cqi, 1.7rem); }
.win-body .case-body       { padding-block: clamp(2rem, 6cqi, 3.4rem); }
.win-body .case-intro      { padding-top: clamp(1.5rem, 4cqi, 2.5rem); }
.win-body .contact-title   { font-size: clamp(1.8rem, 5.5cqi, 2.8rem); }
.win-body .next .np-title  { font-size: clamp(1.35rem, 4cqi, 2rem); }
.win-body .hero-title      { font-size: clamp(2rem, 7cqi, 3.4rem); }

/* Adopted homepage sections: they were written for a full page, so drop the
   full-height hero and the page-sized rhythm once they live in a window. */
.win-body .hero { min-height: 0; padding-block: clamp(1.25rem, 5cqi, 2.5rem); }
/* The hero CTA jumps to a page section that does not exist on the desktop.
   The dock and the Work window are the navigation here. It stays on the
   linear site, where it is still the primary call to action. */
.win-body .hero-actions { display: none; }
.win-body .work,
.win-body .about,
.win-body .recs { padding-block: clamp(1.25rem, 5cqi, 2.5rem); }
.win-body .contact {
  margin-top: 0; border-radius: 0;
  padding-block: clamp(2rem, 7cqi, 3.5rem);
}
.win-body .section-head { margin-bottom: clamp(1.2rem, 4cqi, 2.2rem); }

/* Media queries watch the viewport, so a narrow WINDOW on a wide screen would
   otherwise keep the desktop layout. Container queries fix that. */
@container (max-width: 820px) {
  .win-body .feature { grid-template-columns: 1fr; }
  .win-body .feature-media { min-height: 220px; border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .win-body .feature-body { padding: clamp(1.25rem, 5cqi, 2rem); padding-top: 0; }
  .win-body .about-inner { grid-template-columns: 1fr; }
  .win-body .about-media { max-width: 340px; }
}

/* Resize handles. Hit areas overhang the frame slightly so the edge is
   grabbable without pixel-hunting. */
.win-rz { position: absolute; touch-action: none; }
.win-rz[data-dir="n"] { top: -3px; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }
.win-rz[data-dir="s"] { bottom: -3px; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }
.win-rz[data-dir="w"] { left: -3px; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }
.win-rz[data-dir="e"] { right: -3px; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }
.win-rz[data-dir="nw"] { top: -3px; left: -3px; width: 16px; height: 16px; cursor: nwse-resize; }
.win-rz[data-dir="ne"] { top: -3px; right: -3px; width: 16px; height: 16px; cursor: nesw-resize; }
.win-rz[data-dir="sw"] { bottom: -3px; left: -3px; width: 16px; height: 16px; cursor: nesw-resize; }
.win-rz[data-dir="se"] { bottom: -3px; right: -3px; width: 18px; height: 18px; cursor: nwse-resize; }

/* While dragging: kill pointer events inside embeds, or a cross-origin Figma
   iframe swallows the pointer stream and the window sticks to the cursor. */
html.is-dragging { cursor: grabbing; user-select: none; }
html.is-dragging iframe,
html.is-dragging video { pointer-events: none; }
.os-dragshield { position: fixed; inset: 0; z-index: var(--z-window-drag); }

/* Deliberately NOT scoped under [data-os="on"]: this is the way back into the
   desktop, so it only exists while the desktop is switched off. */
.os-return {
  font: inherit; font-size: .88rem; font-weight: 500;
  color: var(--accent-ink);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: var(--r-pill);
  padding: .45rem .85rem; cursor: pointer; white-space: nowrap;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.os-return:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent);
}

/* Screen-reader-only live region */
.os-live {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- Menu bar ---------- */
.menubar {
  position: fixed; top: 0; left: 0; right: 0; height: 28px;
  z-index: var(--z-menubar);
  display: flex; align-items: center; justify-content: space-between;
  padding-inline: var(--sp-3);
  background: var(--surface-menubar);
  -webkit-backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  border-bottom: 1px solid var(--hairline);
  font-size: var(--fs-ui-md);
}
/* Direct children only. This used to match the nested .menu-pop dropdowns too,
   which forced padding:0 and align-items:center on them, so menu items came
   out centered and shrink-to-fit instead of filling the menu. */
.menubar > ul { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; }
.menu-pop { list-style: none; margin: 0; }
.menubar-left { gap: var(--sp-1); }
.menubar-right { gap: var(--sp-4); color: var(--muted); }
.menubar-right a { color: inherit; }
.menubar-right a:hover { color: var(--ink); }
.menubar-brand { font-weight: 600; padding-inline: var(--sp-2); margin-right: var(--sp-2); }
.menubar-clock { font-variant-numeric: tabular-nums; color: var(--ink); }

.menu { position: relative; }
.menu-btn {
  font: inherit; color: var(--ink);
  background: none; border: 0; cursor: pointer;
  padding: 2px var(--sp-2); border-radius: var(--r-xs);
}
.menu-btn:hover,
.menu-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--ink) 8%, transparent); }

.menu-pop {
  position: absolute; top: calc(100% + 4px); left: 0;
  /* Fixed, not min-width: every dropdown is the same size regardless of the
     longest label it happens to contain. */
  width: 228px; padding: var(--sp-1);
  z-index: var(--z-menu-popover);
  /* Opaque, unlike the rest of the chrome. A menu is a short list of small
     labels and the wallpaper reading through it made them hard to scan; the
     bar and dock keep their vibrancy because nothing has to be read through
     them. --card so dark mode gets its own solid surface, not white. */
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  display: flex; flex-direction: column;
}
.menu-pop[hidden] { display: none; }
.menu-item {
  width: 100%; display: flex; align-items: center; gap: var(--sp-2);
  font: inherit; font-size: var(--fs-ui-lg); text-align: left;
  color: var(--ink); background: none; border: 0; cursor: pointer;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-xs);
}
.menu-item:hover { background: var(--accent); color: #fff; }
.menu-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-check { width: 12px; flex: none; opacity: .8; text-align: center; }
.menu-sep { height: 1px; margin: var(--sp-1) var(--sp-2); background: var(--hairline); }

/* ---------- Desktop icons ---------- */
.desk-icons {
  position: absolute; top: calc(28px + var(--sp-4)); left: var(--sp-4);
  z-index: var(--z-desk-icons);
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3);
}
.desk-icon {
  /* Roomy enough that the longest label ("Recommendations") still sits inside
     the hover highlight rather than overflowing it. */
  width: 104px; padding: var(--sp-3) var(--sp-2);
  display: grid; justify-items: center; gap: var(--sp-2);
  background: none; border: 0; cursor: pointer;
  border-radius: var(--r-md); color: var(--ink);
  transition: background .18s var(--ease);
}
.desk-icon:hover { background: color-mix(in srgb, var(--ink) 9%, transparent); }
.desk-icon-art {
  width: 46px; height: 46px; display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: var(--surface-menu);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-1);
}
.desk-icon-art svg { width: 24px; height: 24px; color: var(--accent-ink); }
.desk-icon-label {
  font-size: var(--fs-ui-xs); line-height: 1.25; text-align: center;
  max-width: 100%;
}

/* ---------- Project launcher (Welcome window) ---------- */
.launcher {
  padding: 0 var(--gutter) clamp(1.25rem, 5cqi, 2rem);
  border-top: 1px solid var(--line);
  margin-top: clamp(.75rem, 3cqi, 1.25rem);
  padding-top: clamp(1.1rem, 4cqi, 1.6rem);
}
.launcher-title {
  font-family: var(--serif); font-weight: 420;
  font-size: clamp(1.05rem, 2.6cqi, 1.25rem); letter-spacing: -.01em;
  color: var(--ink); margin-bottom: var(--sp-4);
}
.launcher-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
@container (max-width: 520px) { .launcher-grid { grid-template-columns: 1fr; } }

.launch-card {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--card); color: var(--ink);
  transition: border-color .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease);
}
.launch-card:hover {
  border-color: var(--ink); transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}
.launch-art {
  flex: none; width: 40px; height: 40px;
  border-radius: var(--r-sm); overflow: hidden;
  display: grid; place-items: center;
  border: 1px solid var(--hairline); background: var(--bg-tint);
}
.launch-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.launch-art img.is-mark { object-fit: contain; padding: 6px; }
.launch-meta { display: grid; gap: 1px; min-width: 0; }
.launch-name { font-size: var(--fs-ui-lg); font-weight: 600; line-height: 1.25; }
.launch-blurb { font-size: var(--fs-ui-sm); color: var(--muted); line-height: 1.3; }
.launch-when { font-size: var(--fs-ui-xs); color: var(--muted-2); }

/* The two paid projects lead: each spans the full grid width and carries more
   weight, so the professional work reads first and the concepts read as
   supporting material rather than as peers. */
.launch-card--lead { grid-column: 1 / -1; padding: var(--sp-4); gap: var(--sp-4); }
.launch-card--lead .launch-art { width: 54px; height: 54px; border-radius: var(--r-md); }
.launch-card--lead .launch-art img.is-mark { padding: 9px; }
.launch-card--lead .launch-meta { gap: 2px; }
.launch-card--lead .launch-name { font-size: 16px; letter-spacing: -.01em; }
.launch-card--lead .launch-blurb { font-size: var(--fs-ui-md); }

/* Concept work: same card, dialled down. */
.launch-card:not(.launch-card--lead) { padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md); }
.launch-card:not(.launch-card--lead) .launch-art { width: 30px; height: 30px; }
.launch-card:not(.launch-card--lead) .launch-art img.is-mark { padding: 4px; }
.launch-card:not(.launch-card--lead) .launch-name { font-size: var(--fs-ui-sm); }
.launch-card:not(.launch-card--lead) .launch-blurb { font-size: var(--fs-ui-xs); }
.launch-card:not(.launch-card--lead) .launch-when { display: none; }
@media (prefers-reduced-motion: reduce) { .launch-card, .launch-card:hover { transform: none; transition: none; } }

/* ---------- Desktop widgets ---------- */
/* Both widget columns live in one top-right rail. The second column is what
   buys Recommendations the width to be readable and takes enough height out
   of the first that the dogs fit at every size the desktop runs at. */
.widgets-rail {
  position: absolute; top: calc(28px + var(--sp-4)); right: var(--sp-4);
  z-index: var(--z-desk-icons);
  display: flex; align-items: flex-start; gap: var(--sp-3);
}
.widgets { display: flex; flex-direction: column; gap: var(--sp-3); }
.widgets--wide { width: 304px; }
.widgets--main { width: 236px; }
/* Under ~1300px the pair eats more than half the desktop, so the wide column
   narrows rather than disappearing: a shorter quote still beats no quote. */
@media (max-width: 1300px) { .widgets--wide { width: 248px; } }
/* The right rail needs ~634px. Below that the playlist goes, not the dogs:
   it is the smallest loss on the wallpaper. Scoped through .widgets so this
   beats the card's own display value regardless of source order. */
@media (max-height: 645px) { .widgets .widget--play { display: none; } }
.widget {
  padding: var(--sp-4);
  border-radius: var(--r-lg);
  background: var(--surface-menu);
  -webkit-backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-2);
}
.widget-label {
  display: block; margin-bottom: var(--sp-3);
  font-size: var(--fs-ui-xs); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
}

/* Experience timeline */
.xp { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-4); }
.xp-item { position: relative; padding-left: var(--sp-5); display: grid; gap: 1px; }
.xp-item::before {
  content: ""; position: absolute; left: 2px; top: 5px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent);
}
/* Connector runs from this dot to the next one. */
.xp-item:not(:last-child)::after {
  content: ""; position: absolute; left: 6px; top: 17px;
  bottom: calc(-1 * var(--sp-4)); width: 1px; background: var(--line);
}
.xp-item.is-current::before {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
.xp-org { font-size: var(--fs-ui-lg); font-weight: 600; color: var(--ink); line-height: 1.3; }
.xp-role { font-size: var(--fs-ui-sm); color: var(--muted); line-height: 1.35; }
.xp-when { font-size: var(--fs-ui-xs); color: var(--muted-2); margin-top: 2px; }

/* Playlist */
.widget--play { padding-bottom: var(--sp-3); }
.play {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-1); margin: calc(-1 * var(--sp-1));
  border-radius: var(--r-sm); color: var(--ink);
  transition: background .18s var(--ease);
}
.play:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); }
.play-art { flex: none; width: 40px; height: 40px; display: grid; place-items: center; }
.play-art svg,
.play-art img { width: 100%; height: 100%; object-fit: contain; display: block; }
.play-meta { display: grid; gap: 1px; min-width: 0; }
.play-title { font-size: var(--fs-ui-md); font-weight: 600; }
.play-note { font-size: var(--fs-ui-xs); color: var(--muted-2); }
.play-go { flex: none; margin-left: auto; color: var(--muted-2); }

/* Recommendations: rotating pull quotes. Sized up for the 320px left rail,
   where a quote reads like a pull quote instead of a caption. */
.widget--recs { padding: var(--sp-5); }
.rq {
  /* A bare <button> otherwise draws the UA border and grey field around the
     whole quote. The card is the frame here; the button is just the hit area. */
  appearance: none; border: 0; background: none; font: inherit; cursor: pointer;
  display: block; width: 100%; text-align: left; color: var(--ink);
  padding: var(--sp-2); margin: calc(-1 * var(--sp-2));
  border-radius: var(--r-sm);
  transition: background .18s var(--ease);
}
.rq:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); }
/* Every quote occupies the same grid cell, so the card is as tall as the
   longest one and never resizes mid-rotation. */
.rq-stack { display: grid; }
.rq-slide {
  grid-area: 1 / 1; display: grid; gap: var(--sp-3);
  opacity: 0; transition: opacity .7s var(--ease);
  pointer-events: none;
}
.rq-slide.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .rq-slide { transition: none; } }
.rq-text {
  font-family: var(--serif); font-weight: 420;
  font-size: 15px; line-height: 1.55; color: var(--ink);
  text-wrap: pretty;
}
.rq-text::before { content: '\201C'; }
.rq-text::after  { content: '\201D'; }
.rq-by { display: flex; align-items: center; gap: var(--sp-2); }
.rq-avatar {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600; letter-spacing: .02em;
  color: var(--accent-ink);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.rq-who { display: grid; gap: 0; min-width: 0; }
.rq-name { font-size: var(--fs-ui-md); font-weight: 600; line-height: 1.3; }
/* Wraps rather than ellipsizing: "Partner Design Director, Micro…" tells a
   reader less than two short lines do, and the grid stack keeps every slide
   the same height anyway. */
.rq-role { font-size: 11px; color: var(--muted-2); line-height: 1.35; }
.rq-foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: var(--sp-3);
}
.rq-dots { display: flex; gap: 5px; }
.rq-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 18%, transparent);
  transition: background .3s var(--ease);
}
.rq-dot.is-on { background: var(--accent); }
.rq-more {
  font-size: 10px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted-2);
}
.rq:hover .rq-more { color: var(--accent-ink); }

/* Pets */
.widget--pets { display: grid; }
.pet-stack {
  position: relative; width: 100%; aspect-ratio: 4 / 3;
  border-radius: var(--r-sm); overflow: hidden;
  margin-bottom: var(--sp-3);
  background: var(--bg-tint);
}
.pet-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
  opacity: 0; transition: opacity .9s var(--ease);
}
.pet-photo.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .pet-photo { transition: none; } }
.pet-names { font-size: var(--fs-ui-md); font-weight: 600; color: var(--ink); }
.pet-note { font-size: var(--fs-ui-xs); color: var(--muted-2); margin-top: 1px; }

/* ---------- Shortcuts sheet ---------- */
.os-sheet {
  position: fixed; inset: 0; z-index: var(--z-sheet);
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 26%, transparent);
}
.os-sheet-panel {
  width: min(460px, 90vw); padding: var(--sp-6);
  background: var(--card); border-radius: var(--r-md);
  border: 1px solid var(--hairline); box-shadow: var(--shadow-win-focus);
}
.os-sheet-panel h2 { font-family: var(--serif); font-weight: 440; font-size: 1.3rem; margin-bottom: var(--sp-4); }
.os-sheet-panel dl { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.os-sheet-panel dl > div { display: flex; gap: var(--sp-4); align-items: baseline; }
.os-sheet-panel dt { flex: none; width: 128px; font-size: var(--fs-ui-sm); font-weight: 600; color: var(--ink); }
.os-sheet-panel dd { font-size: var(--fs-sm); color: var(--muted); }

/* ---------- Dock ---------- */
.dock {
  position: fixed; z-index: var(--z-dock);
  left: 50%; bottom: var(--sp-3); transform: translateX(-50%);
  display: flex; align-items: flex-end; gap: var(--sp-2);
  padding: var(--sp-2);
  border-radius: var(--r-xl);
  background: var(--surface-dock);
  -webkit-backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  backdrop-filter: saturate(var(--vib-sat)) blur(var(--vib-blur));
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-dock);
  list-style: none; margin: 0;
  transition: transform .28s var(--ease), opacity .22s var(--ease);
}
/* Tucked away under a zoomed window; reaching for the bottom edge brings it
   back. translate() keeps the -50% centering that transform already carries. */
.dock.is-hidden {
  transform: translate(-50%, calc(100% + var(--sp-4)));
  opacity: 0;
  pointer-events: none;
}
.dock-list { display: contents; list-style: none; margin: 0; padding: 0; }
.dock-item { position: relative; }
.dock-sep { width: 1px; align-self: stretch; margin-inline: var(--sp-1); background: var(--hairline); }
.dock-glyph { display: grid; place-items: center; width: 100%; height: 100%; }
.dock-glyph svg { width: 26px; height: 26px; color: var(--accent-ink); }
.dock-btn {
  width: 52px; height: 52px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--card);
  overflow: hidden; cursor: pointer;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}
.dock-btn img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dock-btn img.is-mark { object-fit: contain; padding: 9px; }
.dock-btn:hover { transform: translateY(-6px) scale(1.06); box-shadow: var(--shadow-2); }
.dock-btn:active { transform: translateY(-2px) scale(1.02); }

/* Running indicator */
.dock-item::after {
  content: ""; position: absolute; left: 50%; bottom: -5px;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--ink); opacity: 0;
  transform: translateX(-50%);
  transition: opacity .2s var(--ease);
}
.dock-item.is-running::after { opacity: .5; }

.dock-tip {
  position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--surface-menu);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border: 1px solid var(--hairline);
  font-size: var(--fs-ui-sm); white-space: nowrap; color: var(--ink);
  opacity: 0; pointer-events: none;
  transition: opacity .18s var(--ease);
}
.dock-btn:hover + .dock-tip,
.dock-btn:focus-visible + .dock-tip { opacity: 1; }

/* ---------- Window motion ----------
   Scale is applied on an inner wrapper-free element via animation only, so it
   never fights the transform that carries the window's position. */
@keyframes win-in {
  from { opacity: 0; scale: .965; }
  to   { opacity: 1; scale: 1; }
}
@keyframes win-out {
  from { opacity: 1; scale: 1; }
  to   { opacity: 0; scale: .97; }
}
@keyframes win-min {
  from { opacity: 1; scale: 1; }
  to   { opacity: 0; scale: .5; translate: 0 42vh; }
}
.win.is-opening { animation: win-in .26s var(--ease) both; }
.win.is-closing { animation: win-out .16s var(--ease) both; pointer-events: none; }
.win.is-minimizing { animation: win-min .28s var(--ease) both; pointer-events: none; }

/* Dock launch bounce */
@keyframes dock-bounce {
  0%, 100% { transform: translateY(0); }
  30%      { transform: translateY(-14px); }
  55%      { transform: translateY(0); }
  72%      { transform: translateY(-5px); }
}
.dock-item.is-launching .dock-btn { animation: dock-bounce .62s var(--ease); }

/* ---------- Boot ---------- */
.os-boot {
  position: fixed; inset: 0; z-index: var(--z-boot);
  display: grid; place-content: center; justify-items: center; gap: var(--sp-5);
  background: var(--bg);
  transition: opacity .32s var(--ease);
}
.os-boot.is-done { opacity: 0; pointer-events: none; }
.os-boot-mark {
  font-family: var(--serif); font-weight: 380;
  font-size: clamp(1.6rem, 3vw, 2.1rem); letter-spacing: -.02em;
  color: var(--ink);
  opacity: 0; animation: boot-mark .5s var(--ease) .05s both;
}
@keyframes boot-mark { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.os-boot-bar {
  width: 132px; height: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 12%, transparent);
  overflow: hidden;
}
.os-boot-bar i { display: block; height: 100%; width: 0; background: var(--accent); animation: boot-fill .72s var(--ease) .18s both; }
@keyframes boot-fill { from { width: 0; } to { width: 100%; } }

/* ---------- Motion preferences ---------- */
@media (prefers-reduced-motion: reduce) {
  .win, .dock-btn { transition: none; }
  .dock-btn:hover { transform: none; }
  /* Windowing itself is not motion, so the OS stays on; only the movement
     goes away. */
  .win.is-opening, .win.is-closing, .win.is-minimizing,
  .dock-item.is-launching .dock-btn,
  .os-boot-mark, .os-boot-bar i { animation: none !important; }
  .os-boot { display: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .win-bar { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .dock { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
