:root {
  --os-bg:#111819; --os-panel:#182224; --os-accent:#C5E58A; --os-text:#E7ECE8;
  --os-muted:#91A0A5; --os-forest:#20352E; --os-border:#435453;
  --os-radius:12px; --os-gap:16px; --os-titlebar:40px; --os-panel-h:0px; --os-taskbar:48px;
  /* Forest Studio (23 Sep): elevation, not colour, says which window is active — the old lime
     perimeter competed with the content inside it. Cinnamon layout and forest identity unchanged. */
  --os-border-active:#5C7468;
  --os-shadow:0 8px 24px rgba(0,0,0,.38);
  --os-shadow-lift:0 16px 44px rgba(0,0,0,.52);
  /* Category tints for first-party app tiles. Everforest-ish, deliberately desaturated: they read
     as a family at 48px rather than turning every icon the same lime. Real game/extension artwork
     is untouched — these only sit behind transparent glyphs. */
  --os-tint-work:#31473C; --os-tint-build:#2B4444; --os-tint-play:#41402A;
  --r-sm:8px; --r-md:10px; --r-lg:12px;
  --os-taskbar-gap:0px;
  --t-fast:150ms; --t-win:190ms;
  --os-focus:2px solid var(--os-accent);   /* one focus ring for the whole OS */
  --os-font:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; --os-mono:ui-monospace,Menlo,monospace;
}

/* ---- theme presets ----
   Forest is :root above and has no block here on purpose: the default cannot drift when a preset is
   edited. Each preset overrides the SAME fixed token set and nothing else — if a rule needs a colour
   that is not a token, make it a token rather than adding a per-theme component rule. */
body[data-theme="midnight"] {
  --os-bg:#14141F; --os-panel:#1C1C2B; --os-accent:#C4B5FD; --os-text:#E8E8F2;
  --os-muted:#9A9AB4; --os-forest:#282840; --os-border:#3A3A55; --os-border-active:#5A5A7C;
  --os-tint-work:#2F2F4C; --os-tint-build:#25304C; --os-tint-play:#3B2F4C;
}
body[data-theme="arctic"] {
  --os-bg:#2E3440; --os-panel:#3B4252; --os-accent:#88C0D0; --os-text:#ECEFF4;
  --os-muted:#AEB8C8; --os-forest:#434C5E; --os-border:#4C566A; --os-border-active:#72809A;
  --os-tint-work:#3C4A55; --os-tint-build:#3A4659; --os-tint-play:#4A4659;
}
body[data-theme="copper-dusk"] {
  --os-bg:#171514; --os-panel:#24201E; --os-accent:#E9B383; --os-text:#F3EDE5;
  --os-muted:#B9ABA0; --os-forest:#3A2C24; --os-border:#65564D; --os-border-active:#AD8C73;
  --os-tint-work:#39372B; --os-tint-build:#343A3A; --os-tint-play:#40303A;
}

/* The bar's own height plus the gap beneath it when it floats. Everything anchored to the bottom
   reads --os-dock, so switching the panel style moves windows, menus and the calendar together.

   This is declared on `body`, NOT on `:root`, and that is load-bearing. A custom property resolves
   its inner var()s in the context of the element it is DECLARED on — so `--os-dock` on :root would
   capture :root's --os-taskbar-gap (0px) and inherit that frozen value downward, and the floating
   override on body would never reach it. Measured: --os-dock read `calc(48px + 0px)` while the gap
   on body read 10px, and a maximised window overlapped the floating bar by 12px. */
body { --os-dock:calc(var(--os-taskbar) + var(--os-taskbar-gap)); }

/* Theme-aware icon. An <img src="…svg"> canNOT inherit currentColor — the SVG is a separate
   document — so a lime-stroked or currentColor icon stays whatever colour its own file says, which
   is why the app glyphs kept reading lime under Midnight and Arctic. A CSS mask uses only the
   file's ALPHA and paints it with background-color, so the shape follows the theme. Set the file
   with --i. Decorative by definition: always aria-hidden, with real text beside it. */
.i {
  display:inline-block; width:1em; height:1em; flex-shrink:0;
  background-color:currentColor;
  -webkit-mask:var(--i) center/contain no-repeat;
          mask:var(--i) center/contain no-repeat;
}
.btn .i { width:16px; height:16px; margin-right:6px; vertical-align:-3px; }
/* First-party glyphs in the OS chrome. Same mask mechanism as .i; the size comes from the context
   rules below, which used to target the <img> these replaced. */
.glyph {
  display:inline-block; flex-shrink:0; width:24px; height:24px;
  /* --os-accent, not currentColor. Every one of these SVGs has color="#c5e58a" baked into its root,
     so through an <img> they all rendered lime — that IS the Forest look. Painting the mask with the
     accent keeps Forest pixel-identical and makes Midnight and Arctic follow their own accent.
     Action icons inside buttons use .i with currentColor instead, because there the button's text
     colour is the right one. */
  background-color:var(--os-accent);
  -webkit-mask:var(--i) center/contain no-repeat;
          mask:var(--i) center/contain no-repeat;
  pointer-events:none;
}

* { box-sizing:border-box; }
html, body { margin:0; height:100%; background:var(--os-bg); color:var(--os-text); font:15px/1.5 var(--os-font); }
a { color:var(--os-accent); }
button { font:inherit; color:inherit; background:none; border:0; cursor:pointer; min-height:44px; min-width:44px; }
:focus-visible { outline:2px solid var(--os-accent); outline-offset:2px; }
[hidden] { display:none !important; }

/* ---- wallpaper + shell ---- */
body { background: var(--os-bg) url('/public/assets/wallpapers/forest-wallpaper.jpg') center/cover no-repeat fixed; overflow:hidden; }
html.webp body { background-image:url('/public/assets/wallpapers/forest-wallpaper.webp'); }

/* ---- forest wallpaper mist layer ---- */
#mist {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  display: none;
}
body[data-wallpaper="forest"] #mist {
  display: block;
}
#mist::before, #mist::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  width: 300%;
  left: -100%;
}
#mist::before {
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.08) 30%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.08) 70%, transparent);
  animation: mist-drift-1 60s linear infinite;
}
#mist::after {
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.05) 20%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0.05) 80%, transparent);
  animation: mist-drift-2 90s linear infinite;
}

@keyframes mist-drift-1 {
  0% { transform: translateX(-33.33%); }
  50% { transform: translateX(33.33%); }
  100% { transform: translateX(-33.33%); }
}

@keyframes mist-drift-2 {
  0% { transform: translateX(33.33%); }
  50% { transform: translateX(-33.33%); }
  100% { transform: translateX(33.33%); }
}

@media (prefers-reduced-motion: reduce) {
  #mist { display: none !important; }
}
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  #mist { display: none !important; }
}

/* ---- settings wallpaper picker ---- */
.wallpaper-options { display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
.clock-options, .player-options { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:20px; }
.clock-btn-opt, .player-btn { min-height:44px; display:inline-flex; align-items:center; border:1px solid var(--os-border); border-radius:var(--r-sm); padding:0 14px; background:none; transition:border-color var(--t-fast), background-color var(--t-fast); }
.clock-btn-opt[aria-pressed="true"], .player-btn[aria-pressed="true"] { border-color:var(--os-accent); background:var(--os-forest); }

.panel-options { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:20px; }
.panel-btn { min-height:44px; display:inline-flex; align-items:center; gap:10px; border:1px solid var(--os-border); border-radius:var(--r-sm); padding:0 14px 0 10px; background:none; transition:border-color var(--t-fast), background-color var(--t-fast); }
.panel-btn[aria-pressed="true"] { border-color:var(--os-accent); background:var(--os-forest); }
.panel-preview { width:34px; height:24px; border-radius:4px; background:var(--os-bg); border:1px solid var(--os-border); position:relative; flex-shrink:0; }
.panel-preview::after { content:''; position:absolute; background:var(--os-accent); opacity:.75; }
.panel-preview-edge::after { left:0; right:0; bottom:0; height:5px; }
.panel-preview-floating::after { left:3px; right:3px; bottom:3px; height:5px; border-radius:2px; }

.theme-options { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:20px; }
.theme-btn { min-height:44px; display:inline-flex; align-items:center; gap:10px; border:1px solid var(--os-border); border-radius:var(--r-sm); padding:0 14px 0 10px; background:none; transition:border-color var(--t-fast), background-color var(--t-fast); }
.theme-btn[aria-pressed="true"] { border-color:var(--os-accent); background:var(--os-forest); }
.theme-swatch { width:26px; height:26px; border-radius:50%; flex-shrink:0; border:1px solid rgba(255,255,255,.18); }
/* The swatches are literal, not tokens: they must show each theme while the OTHER theme is active. */
.theme-swatch-forest { background:linear-gradient(135deg,#182224 50%,#C5E58A 50%); }
.theme-swatch-midnight { background:linear-gradient(135deg,#1C1C2B 50%,#C4B5FD 50%); }
.theme-swatch-arctic { background:linear-gradient(135deg,#3B4252 50%,#88C0D0 50%); }
.theme-swatch-copper-dusk { background:linear-gradient(135deg,#24201E 50%,#E9B383 50%); }

.wallpaper-btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--os-border); border-radius: 8px; padding: 0 16px; background: none; transition: border-color 0.15s, background-color 0.15s; }
.wallpaper-btn:hover { border-color: var(--os-accent); background: rgba(197,229,138,0.05); }
.wallpaper-btn[aria-pressed="true"] { border-color: var(--os-accent); background: rgba(197,229,138,0.1); font-weight: 600; }
/* One bar, like GNOME/Windows: the taskbar carries brand, tasks, clock and tray. */
.panel-slot { display:contents; }
.panel-loading { color:var(--os-muted); }

.desktop { position:fixed; inset:0 0 var(--os-dock) 0; }

/* ---- windows ---- */
#snap-preview { position:fixed; border:2px solid lime; background:transparent; pointer-events:none; z-index:1500; box-sizing:border-box; }
.win { position:fixed; display:flex; flex-direction:column; background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--os-radius); box-shadow:var(--os-shadow); overflow:hidden; }
.win.is-opening { animation:win-open var(--t-win) ease-out forwards; }
@keyframes win-open { from { transform:scale(0.96); opacity:0; } to { transform:scale(1); opacity:1; } }
.win.is-closing { animation:win-close 160ms ease-in forwards; pointer-events:none; }
@keyframes win-close { from { transform:scale(1); opacity:1; } to { transform:scale(0.96); opacity:0; } }
.win.is-minimizing { animation:win-minimize 220ms ease-in forwards; pointer-events:none; }
@keyframes win-minimize { from { transform:translate(0,0) scale(1); opacity:1; } to { transform:translate(var(--to-x), var(--to-y)) scale(0.2); opacity:0; } }
@media (prefers-reduced-motion: reduce) { .win.is-opening, .win.is-closing, .win.is-minimizing { animation:none !important; } }
/* One quiet border and a lift. Keyboard focus stays on the actual focused control via the
   :focus-visible rule at the top of this file — it is not blanketed by window decoration. */
.win.is-active { border-color:var(--os-border-active); box-shadow:var(--os-shadow-lift); }
.win.is-max { border-radius:0; }
.win-bar { touch-action:none; display:flex; align-items:center; gap:8px; height:var(--os-titlebar); padding:0 4px 0 12px; background:var(--os-bg); border-bottom:1px solid var(--os-border); cursor:grab; user-select:none; touch-action:none; }
.win-bar img, .win-bar .glyph { width:18px; height:18px; }
.win-title { flex:1; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.win-btn { touch-action:manipulation; width:44px; height:44px; margin:-2px 0; border-radius:6px; display:grid; place-items:center;
           position:relative; z-index:2; }  /* above .win-edge-n/-ne, which are later siblings and covered the top 4px of
                                                every title-bar button — a finger landing high on ✕ hit the resize handle,
                                                whose pointerdown preventDefault() then cancelled the tap's click on iOS. */
.win-btn:hover { background:var(--os-forest); }
.win-btn img { width:16px; height:16px; }
.win-body { flex:1; overflow:auto; padding:16px; }
.win-edge { position:absolute; touch-action:none; }
/* The icon fills the middle of a 44px button, so a finger lands on the <img>, not the button.
   iPadOS treats a touch on an image as a possible drag/callout and drops the click. The taskbar
   peek's close button is a text glyph and has always worked on iPad, which is the contrast that
   pointed here. pointer-events:none makes the button itself the only tap target. */
.win-btn img, .pin img, .task img { pointer-events:none; }
.win-btn img, .pin img, .task img, .icon img { -webkit-user-drag:none; -webkit-touch-callout:none; user-select:none; -webkit-user-select:none; }
.win-edge-n, .win-edge-s { left:8px; right:8px; height:6px; cursor:ns-resize; }
.win-edge-e, .win-edge-w { top:8px; bottom:8px; width:6px; cursor:ew-resize; }
.win-edge-n { top:-3px; } .win-edge-s { bottom:-3px; } .win-edge-e { right:-3px; } .win-edge-w { left:-3px; }
.win-edge-ne, .win-edge-nw, .win-edge-se, .win-edge-sw { width:12px; height:12px; }
.win-edge-ne { top:-3px; right:-3px; cursor:nesw-resize; } .win-edge-sw { bottom:-3px; left:-3px; cursor:nesw-resize; }
.win-edge-nw { top:-3px; left:-3px; cursor:nwse-resize; } .win-edge-se { bottom:-3px; right:-3px; cursor:nwse-resize; }
.win.is-max .win-edge { display:none; }

/* ---- app content ---- */
.app h2 { margin:0 0 4px; font-size:28px; line-height:1.2; } .app h3, .win-body h3 { margin:12px 0 8px; font-size:13px; color:var(--os-muted); text-transform:uppercase; letter-spacing:.06em; }
.accent { color:var(--os-accent); } .lead { color:var(--os-muted); margin:0 0 12px; } .note { color:var(--os-muted); font-size:13px; }
.btn { display:inline-flex; align-items:center; gap:6px; min-height:44px; padding:0 16px; border:1px solid var(--os-border); border-radius:8px; color:var(--os-text); text-decoration:none; background:var(--os-bg); }
.btn:hover { border-color:var(--os-accent); } .btn img { width:14px; height:14px; }
.actions { display:flex; flex-wrap:wrap; gap:8px; }
.empty { color:var(--os-muted); padding:24px; text-align:center; border:1px dashed var(--os-border); border-radius:var(--os-radius); }
.chips { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:12px; }
.chip { display:inline-block; padding:4px 10px; min-height:0; min-width:0; border:1px solid var(--os-border); border-radius:999px; font-size:12px; color:var(--os-muted); }
.chip.is-on, button.chip:hover { color:var(--os-bg); background:var(--os-accent); border-color:var(--os-accent); }
button.chip { min-height:44px; }
.btn-primary { background:var(--os-accent); border-color:var(--os-accent); color:var(--os-bg); font-weight:600; }
.btn-primary:hover { background:#d6ef9f; border-color:#d6ef9f; }
.grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)); gap:var(--os-gap); }
/* Every card sits inside a [data-chip] wrapper so the filter chips can hide rows. The grid stretches
   that WRAPPER, not the card, so cards kept their content height and the Play buttons in one row
   landed up to 20px apart (measured 376 vs 396 on the Arcade). Make the wrapper pass the stretch
   through; the card's own `margin-top:auto` on .actions then pins every action to the same line. */
.grid > [data-chip] { display:flex; min-width:0; }
.grid > [data-chip] > .card { flex:1; min-width:0; }
.app-extensions .grid { grid-template-columns:repeat(auto-fill, minmax(320px, 1fr)); }
.card { display:flex; flex-direction:column; background:var(--os-bg); border:1px solid var(--os-border); border-radius:var(--os-radius); overflow:hidden; }
/* The box is already reserved by aspect-ratio, so a card never collapses — verified by removing
   the image and watching the height hold. This only makes the WAIT legible: the same sweep the
   gallery uses, on the container, so it shows through until the cover paints over it. */
.card-media { display:block; aspect-ratio:16/10; background:var(--os-forest); border:none !important;
  background-image:linear-gradient(100deg, var(--os-forest) 38%, var(--os-panel) 50%, var(--os-forest) 62%);
  background-size:220% 100%; animation:shot-shimmer 1.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .card-media { animation:none; background-image:none; } } .card-media img { width:100%; height:100%; object-fit:cover; display:block; border:none !important; }
.card-noshot { display:grid; place-items:center; width:100%; height:100%; min-height:96px; background:var(--os-forest); } .card-noshot img { opacity:.85; }
.card h3 { margin:12px 12px 4px; font-size:16px; text-transform:none; letter-spacing:0; color:var(--os-text); } .card h3 a { color:inherit; text-decoration:none; }
.card p { margin:0 12px 12px; font-size:13px; color:var(--os-muted); }
.card .actions { margin:auto 12px 12px; }
.card-ext { flex-direction:row; align-items:flex-start; gap:12px; padding:12px; }
.card-icon { flex:none; display:block; width:128px; height:128px; border-radius:12px; overflow:hidden; background:var(--os-forest); } .card-icon img { width:100%; height:100%; object-fit:cover; display:block; }
.card-ext .card-body { flex:1; min-width:0; } .card-ext h3, .card-ext p { margin-left:0; margin-right:0; } .card-ext h3 { margin-top:0; } .card-ext .actions { margin:8px 0 0; }
.detail-icon { float:right; margin:0 0 8px 12px; border-radius:12px; }
.card-noshot.detail-icon { width:64px; height:64px; min-height:0; }
.detail-ext-header { display:flex; gap:16px; align-items:flex-start; margin-bottom:20px; }
.detail-ext-icon { float:none; margin:0; flex:none; width:96px; height:96px; border-radius:12px; object-fit:cover; }
.card-noshot.detail-ext-icon { width:96px; height:96px; min-height:0; }
.detail-ext-header-body { flex:1; min-width:0; }
.detail-ext-header-body h2 { margin:0 0 8px !important; }
.detail-ext-header-body .lead { margin:0 0 12px; font-size:15px; color:var(--os-text); }
.detail-ext-header-body .actions { margin:0; }
.detail .back { display:inline-block; margin-bottom:8px; text-decoration:none; color:var(--os-muted); }
.shots { display:flex; gap:12px; overflow-x:auto; margin:16px 0; scroll-snap-type:x mandatory; } .shots img { max-height:360px; border-radius:8px; border:1px solid var(--os-border); scroll-snap-align:start; flex:none; }
.rows { list-style:none; margin:0; padding:0; } .rows li { min-height:40px; display:flex; flex-direction:column; justify-content:center; padding:8px 0; border-bottom:1px solid var(--os-border); } .rows time { margin-left:8px; color:var(--os-muted); font-size:13px; } .rows p { margin:4px 0 0; color:var(--os-muted); }
/* One wrapping row instead of four stacked ones: four 44px rows pushed Email past the window's
   fixed 420px body. Buy Me a Coffee keeps its place but not its weight. */
.rows.connect { display:flex; flex-wrap:wrap; gap:8px; }
.rows.connect li { min-height:0; padding:0; border-bottom:0; }
.rows.connect .btn { min-height:44px; }
.rows.connect .connect-aside { flex-basis:100%; margin-top:2px; }
.rows.connect .connect-aside .btn { min-height:32px; padding:0 8px; border-color:transparent; background:none; color:var(--os-muted); font-size:13px; }
.rows.connect .connect-aside .btn:hover { color:var(--os-accent); }
/* The professional case, under the hook. `role` is the charming line about free browser games;
   this is the one that answers "what is he actually good at", which the 10-second read was
   missing entirely. */
.about-bio { margin:10px 0 0; max-width:62ch; line-height:1.55; }

/* ---- keyboard shortcuts, both platforms ---- */
.shortcuts-table { width:100%; border-collapse:collapse; font-size:13px; }
.shortcuts-table th, .shortcuts-table td { text-align:left; padding:7px 10px; border-bottom:1px solid var(--os-border); vertical-align:middle; }
.shortcuts-table th { font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--os-muted); font-weight:600; }
.shortcuts-table th.is-yours, .shortcuts-table td.is-yours { background:var(--os-forest); }
.shortcuts-table th.is-yours { color:var(--os-text); }
.shortcuts-table .same { color:var(--os-muted); font-style:italic; }
.set-range { display:flex; align-items:center; gap:10px; margin:0 0 4px; }
.set-range input[type=range] { flex:1; max-width:260px; accent-color:var(--os-accent); }
.set-range output { font:12px var(--os-mono); color:var(--os-muted); min-width:42px; }
.set-keys { margin:0; padding-left:18px; }
.set-keys li { margin:3px 0; }
.work-tenure { margin-left:10px; font-size:13px; font-weight:400; color:var(--os-muted); }
.skill-chips { display:flex; flex-wrap:wrap; gap:6px; margin:0; }
.skill-chips .chip { cursor:default; }
.about-head { display:flex; gap:20px; align-items:flex-start; } .avatar { border-radius:50%; flex:none; }
/* ---- first-run intro window ---- */
/* Sized to fit the 560x420 first window without an internal scrollbar: a visitor does not know a
   fixed-height window scrolls, so anything below the fold is simply not there. Every size below is
   a ceiling, not a fixed height -- the window can be resized larger and the content breathes. */
.intro { display:flex; flex-direction:column; gap:14px; }
.intro-head { display:flex; gap:16px; align-items:center; }
.intro-head .avatar { border-radius:50%; flex:none; }
.intro-head h2 { margin:0 0 4px; font-size:21px; line-height:1.2; }
.intro-head .lead { margin:0; font-size:14px; line-height:1.45; }
.intro-what { margin:0; font-size:13px; line-height:1.5; color:var(--os-muted); }
.intro-rows { display:flex; flex-direction:column; gap:2px; }
.intro-row {
  display:flex; gap:12px; align-items:center; padding:7px 8px; text-decoration:none; color:inherit;
  border:1px solid transparent; border-radius:var(--r-sm, 8px);
  transition:background-color .12s, border-color .12s;
}
.intro-row:hover { background:var(--os-forest); border-color:var(--os-border); }
.intro-row-icon { flex:none; display:grid; place-items:center; width:30px; height:30px; border-radius:7px; background:var(--os-forest); color:var(--os-accent); }
.intro-row:hover .intro-row-icon { background:var(--os-panel); }
.intro-row-icon .i { width:17px; height:17px; }
.intro-row-text { display:flex; flex-direction:column; gap:1px; min-width:0; }
.intro-row-head { font-size:13px; font-weight:600; }
/* The count is read from the content at render time, so it is always the real number. */
.intro-row-head em { font-style:normal; font-weight:400; font-size:11px; color:var(--os-accent); margin-left:6px; }
.intro-row-line { font-size:12px; color:var(--os-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.intro-actions { margin:0; }
/* The intro window carries the portrait, name and role already; the desktop card beside it is the
   same thing twice. Hidden only while that window is open -- the preference is never written. */
body.intro-open #about-card { display:none !important; }
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  .intro-head { gap:12px; } .intro-head h2 { font-size:19px; }
  .intro-row-line { white-space:normal; }
}
.timeline { list-style:none; margin:0; padding:0; border-left:2px solid var(--os-border); }
.timeline li { display:grid; grid-template-columns:90px 1fr auto; gap:12px; padding:6px 0 6px 16px; position:relative; }
.timeline li::before { content:""; position:absolute; left:-6px; top:14px; width:10px; height:10px; border-radius:50%; background:var(--os-accent); }
.timeline b { color:var(--os-accent); } .timeline small { color:var(--os-muted); }
kbd { font:12px var(--os-mono); padding:1px 6px; border:1px solid var(--os-border); border-radius:4px; }
/* ---- terminal ---- */
/* Transparency is applied to the WINDOW that holds a terminal, not to .term itself: setting
   opacity on the content would fade the text along with the background, which is unreadable
   rather than atmospheric. A background-color alpha leaves the type fully opaque. */
:root { --os-term-opacity:1; }
/* The body alone was not enough: .win paints its own opaque panel behind it, so a half-transparent
   body simply revealed the window's own background and the setting looked broken. The window goes
   transparent and the body carries the mix, which composites over the wallpaper. The title bar has
   its own background, so the chrome stays solid at every setting. */
.win:has(.term) { background-color:transparent; }
.win:has(.term) > .win-body { background-color:color-mix(in srgb, var(--os-panel) calc(var(--os-term-opacity) * 100%), transparent); }
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  /* Older engines keep the solid panel rather than losing the background entirely. */
  .win:has(.term) > .win-body { background-color:var(--os-panel); }
}
.term { display:flex; flex-direction:column; height:100%; font:14px/1.5 var(--os-mono); }
.t-out .t-dir { color:var(--os-accent); }
.t-out { flex:1; margin:0; white-space:pre-wrap; overflow:auto; }
.t-form { display:flex; gap:8px; border-top:1px solid var(--os-border); padding-top:8px; }
.t-in { flex:1; background:none; border:0; color:var(--os-text); font:inherit; outline:none; }
.t-prompt { display:inline-flex; white-space:nowrap; pointer-events:none; }
.t-dir { color:var(--os-accent); }
.t-err { color:color-mix(in srgb, var(--os-accent) 30%, #e06c75); }
.t-user { color:var(--os-accent); }
.t-cwd { color:var(--os-muted); }

/* ---- frame (browser / game / radio) ---- */
.win[data-id="browser"] .win-body, .win[data-id="radio"] .win-body, .win[data-id^="games:"] .win-body { padding:0; }
/* .win-body's child .app div has no explicit height, so .frame's height:100% has no definite ancestor to
   resolve against (a % height against an auto-height block computes as auto) — the iframe silently collapses
   to 0px tall. Give the windows that mount a frame a definite height chain down to it. */
.win[data-id="browser"] .win-body > .app, .win[data-id="radio"] .win-body > .app, .win[data-id^="games:"] .win-body > .app { height:100%; }
.frame { display:flex; flex-direction:column; height:100%; }
.frame-tools { display:flex; align-items:center; gap:6px; padding:6px 8px; border-bottom:1px solid var(--os-border); background:var(--os-panel); flex-wrap:wrap; }
.frame-btn { min-width:44px; padding:0 10px; }
.frame-url { flex:1; min-width:120px; padding:0 10px; min-height:44px; border:1px solid var(--os-border); border-radius:8px; background:var(--os-bg); color:var(--os-text); font:13px var(--os-mono); box-sizing:border-box; outline:none; }
.frame-controls { margin:0; padding:6px 12px; border-bottom:1px solid var(--os-border); }
.frame-body { position:relative; flex:1; min-height:0; background:#000; }
.frame-iframe { position:absolute; inset:0; width:100%; height:100%; border:0; }
.frame-skeleton { position:absolute; inset:0; display:grid; place-items:center; gap:12px; background:var(--os-forest); color:var(--os-muted); z-index:1; }
.frame-skeleton img { opacity:.85; }
.frame-radio .frame-tools { justify-content:flex-end; }

/* Start Page Styles */
.frame-start { position:absolute; inset:0; overflow-y:auto; padding:24px; background:var(--os-forest); color:var(--os-text); display:flex; flex-direction:column; gap:24px; }
.frame-start h2 { margin:0; font-size:24px; font-weight:600; letter-spacing:-0.5px; border-bottom:1px solid var(--os-border); padding-bottom:8px; }
.start-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(140px, 1fr)); gap:16px; }
.start-tile { background:var(--os-panel); border:1px solid var(--os-border); border-radius:12px; padding:16px; display:flex; flex-direction:column; align-items:center; text-align:center; gap:8px; cursor:pointer; transition:background 0.15s, transform 0.1s; }
.start-tile:hover { background:color-mix(in srgb, var(--os-accent) 9%, transparent); transform:translateY(-2px); }
.start-tile:active { transform:translateY(0); }
.tile-icon-wrap { width:48px; height:48px; display:flex; align-items:center; justify-content:center; background:var(--os-bg); border-radius:8px; overflow:hidden; }
.tile-icon-wrap img { width:100%; height:100%; object-fit:cover; }
.tile-title { font-size:13px; font-weight:500; line-height:1.2; color:var(--os-text); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.tile-host { font-size:11px; color:var(--os-muted); font-family:var(--os-mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; width:100%; }
.start-footer { margin-top:auto; display:flex; justify-content:center; gap:24px; padding-top:16px; border-top:1px solid var(--os-border); }
.start-footer a { font-size:13px; color:var(--os-muted); text-decoration:none; transition:color 0.15s; }
.start-footer a:hover { color:var(--os-text); text-decoration:underline; }
.radio-blocked { padding:var(--os-gap); text-align:center; } .radio-blocked .actions { justify-content:center; }
.frame-blocked-panel { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:24px; background:var(--os-forest); color:var(--os-text); text-align:center; }
.frame-blocked-panel p { margin:0 0 16px 0; max-width:480px; line-height:1.5; }
.frame-blocked-panel p strong { font-weight:600; }
.frame-blocked-panel .actions { display:flex; gap:12px; justify-content:center; margin-top:12px; }

/* Music App Styles */
.app-music { height: 100%; }
.music-app { display: flex; height: 100%; background: var(--os-forest); color: var(--os-text); overflow: hidden; }
.music-sidebar { width: 320px; border-right: 1px solid var(--os-border); background: var(--os-panel); display: flex; flex-direction: column; overflow-y: auto; }
.music-section-title { padding: 12px 16px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--os-muted); letter-spacing: 1px; background: rgba(0,0,0,0.1); border-bottom: 1px solid var(--os-border); border-top: 1px solid var(--os-border); }
.music-section-title:first-child { border-top: none; }
.music-list { display: flex; flex-direction: column; }
.music-item-btn { display: flex; align-items: center; gap: 12px; padding: 8px 16px; border: none; background: none; color: var(--os-text); text-align: left; cursor: pointer; border-bottom: 1px solid var(--os-border); transition: background-color 0.15s; width: 100%; box-sizing: border-box; }
.music-item-btn:hover { background: color-mix(in srgb, var(--os-accent) 9%, transparent); }
.music-item-btn.is-active { background: rgba(197, 229, 138, 0.15); border-left: 3px solid var(--os-accent, #C5E58A); padding-left: 13px; }
.music-item-thumb { border-radius: 4px; object-fit: cover; background: #000; flex-shrink: 0; }
.song-thumb { width: 96px; height: 54px; }
.short-thumb { width: 54px; height: 96px; }
.music-item-title { font-size: 13px; font-weight: 500; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--os-text); }
.music-player-area { flex: 1; display: flex; flex-direction: column; padding: 24px; gap: 16px; overflow-y: auto; align-items: center; justify-content: space-between; }
.music-player-container { position: relative; background: #000; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 16/9; }
/* Same treatment as .is-short, and for the same reason: a shrinkable flex item loses its
   aspect-ratio. Left alone, a song came out 598x182 — under YouTube's 200px minimum. Sizing by
   height with the width following keeps the ratio true at any window size. */
.music-player-container.is-song {
  aspect-ratio: 16/9;
  flex: 0 0 auto;
  width: auto;
  height: min(360px, 100%);
  max-width: 640px;
  max-height: 100%;
}
/* A portrait video must not be squashed to fit the column. `aspect-ratio: 9/16` was set and
   ignored: the container is a flex item with the default `flex-shrink: 1`, so in a 586px-tall
   area it was compressed to 300x164 — landscape, with the ratio lost — while the width sat at its
   300px cap. 164px high is also BELOW YouTube's 200x200 minimum for an embed, so this was a
   compliance problem and not only a cosmetic one. Size it by height and let the width follow. */
.music-player-container.is-short {
  aspect-ratio: 9/16;
  flex: 0 0 auto;
  width: auto;
  height: min(520px, 100%);
  max-width: 300px;
  max-height: 100%;
}
.music-preview-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.music-preview-thumb { width: 100%; height: 100%; object-fit: cover; opacity: 0.6; }
.music-play-btn { position: absolute; width: 64px; height: 64px; border-radius: 50%; background: var(--os-accent, #C5E58A); border: none; color: #000; font-size: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: transform 0.15s, background-color 0.15s; }
/* Was a hardcoded lime, which stayed lime under every other preset. Brightening the accent keeps
   the Forest hover identical and makes the other themes follow their own colour. */
.music-play-btn:hover { transform: scale(1.1); filter: brightness(1.12); }
/* The IFrame API replaces the mount div with its own iframe, so this targets the element rather
   than a class we control. */
.music-player-container iframe, .music-iframe { width: 100%; height: 100%; border: 0; }
.music-mount { width: 100%; height: 100%; }
.music-info { text-align: center; max-width: 640px; display: flex; flex-direction: column; gap: 12px; align-items: center; }
.music-track-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 1.4; color: var(--os-text); }
.music-state { margin: 0; font-size: 13px; color: var(--os-muted); min-height: 1.2em; }

/* ---- transport ----
   Only the controls the provider actually supports; each is hidden rather than disabled when it
   is not available, because a dead control is worse than an absent one. */
.music-transport { display:flex; align-items:center; gap:10px; width:100%; max-width:640px; }
.music-transport[hidden] { display:none; }
.mt-btn { min-width:40px; min-height:40px; display:grid; place-items:center; border:1px solid var(--os-border); border-radius:var(--r-sm); background:var(--os-panel); color:var(--os-text); cursor:pointer; font-size:13px; }
.mt-btn:hover:not(:disabled) { background:var(--os-forest); }
.mt-btn:disabled { opacity:.45; cursor:default; }
.mt-time { font:12px var(--os-mono); color:var(--os-muted); min-width:38px; text-align:center; font-variant-numeric:tabular-nums; }
.mt-seek { flex:1; min-width:60px; accent-color:var(--os-accent); }
.mt-vol { display:flex; align-items:center; }
.mt-volume { width:84px; accent-color:var(--os-accent); }

/* ---- mini player ----
   The same window, resized. The sidebar, the channel links and the "listen elsewhere" block step
   out; the video and the transport stay, and the video keeps well clear of YouTube's 200x200
   minimum with nothing drawn over it. */
.music-app.is-compact .music-sidebar,
.music-app.is-compact .music-channel-footer,
.music-app.is-compact .music-elsewhere,
.music-app.is-compact .music-track-links { display:none; }
.music-app.is-compact { grid-template-columns:1fr; }
/* Measured before changing it: at 380x300 the body was 258px tall, the video took its 200px
   minimum, and the transport landed at y=464 against a body ending at 411 — the controls were
   BELOW THE FOLD in a mini player, which is the one thing it cannot afford. The window is 366px
   now and the area does not scroll. */
.music-app.is-compact .music-player-area { padding:8px 10px 10px; gap:6px; justify-content:flex-start; overflow:hidden; }
.music-app.is-compact .music-state { display:none; }   /* the play/pause icon already says it */
/* Compact deliberately keeps the 16:9 box even for a Short: at 9/16 inside a 380px window the
   video would come out about 113px wide, under YouTube's 200px minimum. The short letterboxes
   inside a compliant box instead. */
.music-app.is-compact .music-player-container,
.music-app.is-compact .music-player-container.is-short { max-width:100%; width:auto; height:auto; aspect-ratio:16/9; min-height:200px; flex:0 0 auto; }
.music-app.is-compact .music-track-title { font-size:13px; line-height:1.3; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
/* Volume was hidden here to keep the transport on one row. It is the control people reach for
   most in a mini player, so the row wraps instead and the slider keeps its place. */
.music-app.is-compact .music-transport { flex-wrap:wrap; row-gap:6px; }
.music-app.is-compact .mt-vol { display:flex; align-items:center; flex:1 1 100%; min-width:0; gap:8px; }
.music-app.is-compact .mt-volume { width:100%; }
.music-app.is-compact .mt-time { min-width:32px; }
.music-track-links { display: flex; justify-content: center; }
.music-elsewhere { width: 100%; max-width: 640px; text-align: center; }
.music-elsewhere h4 { margin: 0 0 4px; font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--os-muted); }
.music-elsewhere .note { margin: 0 0 8px; font-size: 12px; color: var(--os-muted); }
.music-elsewhere-links { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.music-channel-footer { display: flex; justify-content: center; gap: 24px; width: 100%; padding-top: 16px; border-top: 1px solid var(--os-border); margin-top: auto; }
.music-channel-footer a { font-size: 13px; color: var(--os-muted); text-decoration: none; transition: color 0.15s; }
.music-channel-footer a:hover { color: var(--os-text); text-decoration: underline; }

@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  .music-app { flex-direction: column; }
  .music-sidebar { width: 100%; height: 240px; border-right: none; border-bottom: 1px solid var(--os-border); }
  .music-player-area { padding: 16px; }
}

/* ---- game window ---- */
.game { height:100%; } .game .frame { height:100%; }
.game-blocked { padding:var(--os-gap); overflow:auto; height:100%; }
.game-cover { aspect-ratio:16/10; max-width:560px; border-radius:8px; overflow:hidden; background:var(--os-forest); margin-bottom:12px; } .game-cover img { width:100%; height:100%; object-fit:cover; display:block; }
.toast { position:fixed; left:50%; bottom:calc(var(--os-dock) + 16px); transform:translateX(-50%); padding:10px 16px; background:var(--os-panel); border:1px solid var(--os-accent); border-radius:8px; z-index:1200; font-size:14px; box-shadow:0 8px 24px rgba(0,0,0,.5); }
@media (prefers-reduced-motion: no-preference) { .toast { animation: toast-in .15s ease-out; } @keyframes toast-in { from { opacity:0; transform:translate(-50%, 8px); } } }

/* ---- bottom panel ---- */
.taskbar { position:fixed; left:0; right:0; bottom:0; height:var(--os-taskbar); display:flex; align-items:center; gap:4px; padding:0 8px; background:var(--os-panel); border-top:1px solid var(--os-border); z-index:1000; }
/* ---- floating panel (opt-in) ----
   Design review item 3: "a nearly full-width floating bottom panel with 8–12px margins, 48–52px
   height and 12px corners as a REVIEWABLE option". Left launcher / open tasks / right date-time all
   keep their places — only the bar's frame changes. Edge-to-edge stays the default. */
/* The bar is 50px when floating (the review asks for 48–52). That goes through --os-taskbar rather
   than a height override, so --os-dock counts it: hardcoding height:50px left the dock at 58 while
   the bar occupied 60, and a maximised window overlapped it by exactly 2px. */
body[data-panel="floating"] { --os-taskbar-gap:10px; --os-taskbar:50px; }
body[data-panel="floating"] .taskbar {
  left:10px; right:10px; bottom:var(--os-taskbar-gap);
  border:1px solid var(--os-border);
  border-radius:var(--r-lg);
  box-shadow:var(--os-shadow-lift);
}
/* A phone or a finger wants the full edge: a floating bar there wastes width and puts a 10px
   dead strip under a 44px target. The design review asks for practical edge-to-edge behaviour. */
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  body[data-panel="floating"] { --os-taskbar-gap:0px; }
  body[data-panel="floating"] .taskbar { left:0; right:0; bottom:0; border-radius:0; border:0; border-top:1px solid var(--os-border); }
}

.apps-btn { display:inline-flex; align-items:center; gap:2px; /* the mark IS the R — it sits tight against 'ohit OS' */ height:44px; padding:0 12px; border-radius:8px; font-weight:600; }
.apps-btn:hover, .apps-btn[aria-expanded="true"] { background:var(--os-forest); }
.pinned { display:flex; gap:2px; padding:0 4px; border-right:1px solid var(--os-border); margin-right:4px; }
.pin { width:44px; height:44px; display:grid; place-items:center; border-radius:8px; position:relative; }
.pin:hover { background:var(--os-forest); } .pin img, .pin .glyph { width:22px; height:22px; }
.pin.is-open::after { content:""; position:absolute; left:14px; right:14px; bottom:2px; height:3px; border-radius:2px; background:var(--os-accent); }
.tasks { flex:1; display:flex; gap:4px; overflow-x:auto; min-width:0; }
.task { display:inline-flex; align-items:center; gap:8px; height:44px; max-width:220px; padding:0 12px; border-radius:8px; color:var(--os-muted); border-bottom:2px solid transparent; }
.task span { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .task img, .task .glyph { width:20px; height:20px; flex:none; }

/* ---- Now Playing strip ----
   Present only while a track is loaded; `hidden` otherwise, so the bar looks exactly as it did
   before when nothing is playing. It shows state and focuses the player — it never plays here. */
.nowplaying { display:flex; align-items:center; gap:8px; max-width:280px; padding:0 8px; border-left:1px solid var(--os-border); }
.nowplaying[hidden] { display:none; }
.np-art { width:28px; height:28px; border-radius:6px; object-fit:cover; flex:none; }
.np-title { display:flex; flex-direction:column; align-items:flex-start; gap:1px; min-width:0; background:none; border:0; padding:2px 4px; border-radius:var(--r-sm); color:var(--os-text); cursor:pointer; font:inherit; text-align:left; }
.np-title span { font-size:12px; max-width:150px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.np-title small { font-size:10px; color:var(--os-muted); }
.np-title:hover { background:var(--os-forest); }
.np-toggle { font-size:12px; white-space:nowrap; }
@media (max-width: 1023px) { .nowplaying { max-width:52px; } .np-title { display:none; } }

/* ---- the hover card ----
   Opens upward from the strip. It shows STATE, not video: an iframe cannot be in two places, and
   moving it here would reload and restart the track, so the video stays in its window and this is
   the remote control for it. */
.nowplaying { position:relative; }
.np-card {
  position:absolute; bottom:calc(100% + 8px); right:0; width:260px; padding:12px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--os-radius);
  box-shadow:var(--os-shadow-lift); z-index:1200;
  opacity:0; visibility:hidden; transform:translateY(4px);
  transition:opacity var(--t-fast) ease, transform var(--t-fast) ease, visibility var(--t-fast);
}
.nowplaying:hover .np-card,
.nowplaying:focus-within .np-card { opacity:1; visibility:visible; transform:none; }
.np-card-art { width:100%; height:120px; object-fit:cover; border-radius:var(--r-sm); display:block; }
.np-card-title { margin:8px 0 2px; font-size:13px; font-weight:600; line-height:1.3; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.np-card-state { margin:0; font-size:11px; color:var(--os-muted); }
.np-card-bar { height:4px; border-radius:2px; background:var(--os-forest); margin:8px 0 4px; overflow:hidden; }
.np-card-bar span { display:block; height:100%; background:var(--os-accent); transition:width .4s linear; }
.np-card-time { display:flex; justify-content:space-between; margin:0; font:11px var(--os-mono); color:var(--os-muted); }
.np-card-actions { display:flex; gap:6px; margin:10px 0 0; }
.np-card-actions .btn { flex:1; justify-content:center; min-height:34px; font-size:12px; }
@media (prefers-reduced-motion: reduce) { .np-card { transition:none; } }
.task:hover { background:var(--os-forest); color:var(--os-text); }
.task.is-active { color:var(--os-text); background:var(--os-forest); border-bottom-color:var(--os-accent); }
.task.is-min { opacity:.6; }
.tray-btn { min-width:44px; height:44px; display:grid; place-items:center; border-radius:8px; color:var(--os-muted); }
.tray-btn .glyph { width:20px; height:20px; }
.apps-btn .glyph { width:24px; height:24px; }
.tray-btn:hover, .tray-btn[aria-pressed="true"], .tray-btn[aria-expanded="true"] { background:var(--os-forest); color:var(--os-text); }
.clock-btn { padding:0; }
.clock {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  text-align: right;
  font-variant-numeric: tabular-nums;
  min-width: 90px;
  padding: 0 10px;
  line-height: 1.2;
}
.clock-time {
  font-size: 13px;
  color: var(--os-text);
  font-weight: 600;
}
.clock-date {
  font-size: 10px;
  color: var(--os-muted);
}
/* ---- application menu ---- */
.appmenu {
  position: fixed;
  left: 8px;
  bottom: calc(var(--os-dock) + 8px);
  width: min(600px, calc(100vw - 16px));
  height: min(540px, calc(100vh - var(--os-dock) - 24px));
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  background: var(--os-panel);
  border: 1px solid var(--os-border);
  border-radius: var(--os-radius);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .5);
  z-index: 1100;
  box-sizing: border-box;
}
.menu-body {
  display: grid;
  grid-template-columns: 56px 150px 1fr;
  grid-template-rows: 1fr auto;
  gap: 12px;
  flex: 1;
  min-height: 0;
}
.menu-rail {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  border-right: 1px solid var(--os-border);
  padding-right: 8px;
}
.rail-avatar-container {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 4px;
}
.rail-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
}
.rail-initials {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--os-forest);
  color: var(--os-text);
  font-weight: bold;
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The launcher mark is the letter R, so the button reads [R]ohit OS; the hidden R keeps the
   accessible name and any text copy intact. */
.vh { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.apps-btn-word { letter-spacing:.01em; }
.rail-btn-session { margin-top:auto; }
.rail-btn {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--os-muted);
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0;
}
.rail-btn:hover {
  background: var(--os-forest);
  color: var(--os-text);
}
.rail-btn:focus-visible {
  background: var(--os-forest);
  color: var(--os-text);
  outline: var(--os-focus);
  outline-offset: -2px;
}
.menu-cats {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-right: 1px solid var(--os-border);
  padding-right: 8px;
}
.menu-cat {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  text-align: left;
  padding: 0 12px;
  border-radius: 8px;
  color: var(--os-muted);
  background: none;
  border: 0;
  width: 100%;
  cursor: pointer;
  font-size: 13px;
}
.menu-cat img, .menu-cat .glyph {
  width: 16px;
  height: 16px;
  opacity: 0.7;
}
.menu-cat:hover img, .menu-cat.is-on img {
  opacity: 1;
}
.menu-cat:hover, .menu-cat.is-on {
  background: var(--os-forest);
  color: var(--os-text);
}
.menu-cat.is-on { box-shadow: inset 3px 0 0 var(--os-accent); }
.menu-cat:focus-visible {
  background: var(--os-forest);
  color: var(--os-text);
  outline: var(--os-focus);
  outline-offset: -2px;
}
.menu-search {
  grid-column: 2 / span 2;
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  min-height: 44px;
  border: 1px solid var(--os-border);
  border-radius: 8px;
  background: var(--os-bg);
}
.menu-search input {
  flex: 1;
  background: none;
  border: 0;
  color: var(--os-text);
  font: inherit;
  outline: none;   /* the ring is drawn on .menu-search, which is the visible control */
  min-height: 42px;
}
.menu-search:focus-within { outline: var(--os-focus); outline-offset: -2px; }
.menu-list {
  grid-column: 3;
  grid-row: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.menu-row {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  text-align: left;
  color: var(--os-text);
  background: none;
  border: 0;
  width: 100%;
  cursor: pointer;
  box-sizing: border-box;
}
.menu-row-icon {
  width: 26px;
  height: 26px;
  border-radius: 4px;
  object-fit: cover;
  flex-shrink: 0;
}
.menu-row-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  flex: 1;
}
.menu-row-name {
  font-weight: 500;
  font-size: 13px;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.menu-row-desc {
  font-size: 12px;
  color: var(--os-muted);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.menu-row { transition: background var(--t-fast) ease; }
.menu-row:hover, .menu-row[aria-selected="true"] { background: var(--os-forest); }
/* Three states, three appearances. They were one: hover, keyboard focus and "this is the row the
   arrow keys are on" all painted the same --os-forest fill and nothing else, so a keyboard user
   could not see where focus was, nor tell focus from a mouse hover. Measured, the fill alone is
   1.25:1 against the menu panel — WCAG 2.2 wants 3:1 for a focus indicator. The accent outline
   measures 11.56:1. */
.menu-row[aria-selected="true"] { box-shadow: inset 3px 0 0 var(--os-accent); }
.menu-row:focus-visible {
  background: var(--os-forest);
  outline: var(--os-focus);
  outline-offset: -2px;
}
.menu-empty {
  font-size: 13px;
  color: var(--os-muted);
  padding: 12px;
  text-align: center;
}
.menu-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  border-top: 1px solid var(--os-border);
  padding-top: 12px;
}
.menu-foot-link {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: 6px 12px;
  border-radius: 8px;
  color: var(--os-text);
  text-decoration: none;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--os-border);
  flex: 1;
  box-sizing: border-box;
}
.menu-foot-link:hover { background: var(--os-forest); }
.menu-foot-link:focus-visible {
  background: var(--os-forest);
  outline: var(--os-focus);
  outline-offset: -2px;
}
.menu-foot-primary {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.2;
}
.menu-foot-secondary {
  font-size: 10px;
  color: var(--os-muted);
  line-height: 1.2;
}
.menu-foot-link:hover .menu-foot-secondary, .menu-foot-link:focus-visible .menu-foot-secondary {
  color: var(--os-text);
}
/* ---- calendar ---- */
.calendar { position:fixed; right:8px; bottom:calc(var(--os-dock) + 8px); width:300px; padding:12px; background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--os-radius); box-shadow:0 12px 40px rgba(0,0,0,.5); z-index:1100; }
.cal-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; } .cal-nav { width:44px; height:44px; border-radius:8px; } .cal-nav:hover { background:var(--os-forest); }
.cal-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:2px; text-align:center; font-size:13px; }
.cal-dow { color:var(--os-muted); font-size:11px; padding:4px 0; } .cal-day { padding:6px 0; border-radius:6px; min-height:32px; }
.cal-day.is-today { background:var(--os-accent); color:var(--os-bg); font-weight:600; }
@media (max-width:767px) {
  /* The phone shell replaces all of this; a visitor who chose the desktop keeps it. */
  body:not(.force-desktop) :is(.taskbar, .calendar, .panel) { display:none; }
  body:not(.force-desktop) .appmenu:not([hidden]) {
    display: flex !important;
  }
  .appmenu {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    height: min(80vh, calc(100vh - 24px));
    border-radius: var(--os-radius) var(--os-radius) 0 0;
    flex-direction: column;
    gap: 12px;
  }
  .menu-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .menu-rail {
    display: none;
  }
  .menu-cats {
    flex-direction: row;
    overflow-x: auto;
    border-right: none;
    border-bottom: 1px solid var(--os-border);
    padding-right: 0;
    padding-bottom: 8px;
    white-space: nowrap;
    flex-shrink: 0;
  }
  .menu-cat {
    /* 36px was below the 44 every other control in the OS holds, on the one layout that is only
       ever seen on a touch device. Trimming the padding to 12 also buys 40px across the five
       chips, which is more than the 18px the row was overflowing by -- so "System" stops being
       cut off the right edge and the row no longer needs a swipe to reach its last item. */
    min-height: 44px;
    padding: 0 12px;
    width: auto;
    display: inline-flex;
  }
  .menu-list {
    flex: 1;
    min-height: 0;
  }
}
/* ---- desktop icons ---- */
.icons { position:absolute; inset:0; overflow:hidden; }
.icon { position:absolute; width:80px; height:96px; min-width:0; min-height:0; display:flex; flex-direction:column; align-items:center; gap:4px; padding:6px 4px; border-radius:8px; color:var(--os-text); font-size:11px; line-height:1.2; text-shadow:0 1px 2px rgba(0,0,0,.85); touch-action:none; user-select:none; -webkit-user-select:none; }
.icon:hover { background:rgba(32,53,46,.55); }
.icon[aria-selected="true"] { outline:2px solid var(--os-accent); outline-offset:0; background:rgba(32,53,46,.7); }
.icon-img { width:48px; height:48px; border-radius:var(--r-md); overflow:hidden; display:grid; place-items:center; background:var(--os-forest); box-shadow:0 2px 6px rgba(0,0,0,.4); }
/* Category tints. Grouped by what the app IS, matching the launcher's Work/Play/Build/System. */
.icon[data-icon="about"] .icon-img, .icon[data-icon="work"] .icon-img { background:var(--os-tint-work); }
.icon[data-icon="projects"] .icon-img, .icon[data-icon="extensions"] .icon-img, .icon[data-icon="articles"] .icon-img { background:var(--os-tint-build); }
.icon[data-icon="games"] .icon-img, .icon[data-icon="music"] .icon-img, .icon[data-icon="radio"] .icon-img { background:var(--os-tint-play); }
/* The phone grid gets the same family. Exact-match data-open only, so an item tile
   (data-open="games/garudastra", which carries real cover art) is never tinted. */
.launcher-btn[data-open="about"] .launcher-icon, .launcher-btn[data-open="work"] .launcher-icon { background:var(--os-tint-work); }
.launcher-btn[data-open="projects"] .launcher-icon, .launcher-btn[data-open="extensions"] .launcher-icon, .launcher-btn[data-open="articles"] .launcher-icon { background:var(--os-tint-build); }
.launcher-btn[data-open="games"] .launcher-icon, .launcher-btn[data-open="music"] .launcher-icon, .launcher-btn[data-open="radio"] .launcher-icon { background:var(--os-tint-play); }
.icon-img img { width:100%; height:100%; object-fit:cover; display:block; } .icon-img .icon-glyph { width:28px; height:28px; object-fit:contain; }
.icon-img span.icon-glyph { width:28px; height:28px; }
.icon-label { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; text-align:center; overflow-wrap:anywhere; }
@media (max-width:767px) { body:not(.force-desktop) .icons { display:none; } }

/* ---- mobile launcher (<768px) ---- */
/* The wallpaper was ALREADY being fetched on phones — `body` carries it — and then covered by an
   opaque `--os-panel` fill, so the one thing that makes this site memorable was paid for and
   thrown away. Measured on a 375px viewport: forest-wallpaper.webp downloaded, 49KB, invisible.
   A scrim instead of a blockout: heaviest at the top where the header text sits directly on it,
   lifting to .45 behind the grid, where the tiles and the identity card carry their own panel
   backgrounds and stay legible over any wallpaper.

   Tuned against the BRIGHT end, not the default. Forest is a night scene — sampled luminance 24–40
   of 255 across the phone crop — so it reads dark at any scrim, which is the intent. Arctic
   Porcelain is the one that would wash out the header, so the values were set there and checked
   for legibility, then confirmed not to flatten the darker wallpapers. */
.mobile { position:fixed; inset:0; overflow:auto; background:linear-gradient(180deg, rgba(17,24,25,.86) 0%, rgba(17,24,25,.55) 26%, rgba(17,24,25,.45) 100%); }
.mobile .launcher-btn { background:var(--os-panel); }
.m-head { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:20px 16px 4px; }
.m-title { margin:0; font-size:22px; }
.m-simple { display:inline-flex; align-items:center; min-height:44px; padding:0 4px; color:var(--os-muted); font-size:13px; text-decoration:none; }
.m-simple:hover { color:var(--os-accent); }
/* ---- mobile identity card ----
   The phone home page had no name, role or contact on it at all. This is the smallest thing that
   answers "whose is this and how do I reach them" before the app grid starts. */
.m-id { display:grid; grid-template-columns:auto 1fr; gap:4px 12px; align-items:center; margin:8px 16px 16px; padding:14px; background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--os-radius); }
.m-id-avatar { grid-row:1; width:64px; height:64px; border-radius:50%; object-fit:cover; }
.m-id-text { grid-column:2; min-width:0; }
.m-id-text h2 { margin:0; font-size:19px; line-height:1.2; }
.m-id-text p { margin:4px 0 0; font-size:13px; line-height:1.45; color:var(--os-muted); }
.m-id-actions { grid-column:1/-1; display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
.m-id-actions .btn { flex:1 1 auto; justify-content:center; min-height:44px; }
.launcher { display:grid; grid-template-columns:repeat(3, 1fr); gap:12px; padding:16px; }
.launcher-btn { display:flex; flex-direction:column; align-items:center; gap:8px; min-height:88px; padding:16px 4px; border-radius:var(--os-radius); background:var(--os-bg); color:var(--os-text); text-decoration:none; font-size:12px; text-align:center; }
.launcher-icon { width:40px; height:40px; display:grid; place-items:center; border-radius:10px; overflow:hidden; background:var(--os-forest); }
.launcher-icon img { width:100%; height:100%; object-fit:cover; display:block; }
/* Game covers fill the tile; a first-party glyph sits inside it at the old <img> size. */
.launcher-icon .glyph { width:26px; height:26px; }
.m-bar { position:sticky; top:0; display:flex; align-items:center; gap:8px; height:48px; padding:0 8px; background:var(--os-bg); border-bottom:1px solid var(--os-border); z-index:1; }
.m-back { display:grid; place-items:center; width:44px; height:44px; border-radius:8px; }
.m-back:hover { background:var(--os-forest); }
.m-back img { width:20px; height:20px; }
.m-body { padding:16px; }
@media (max-width:767px) {
  /* Windows are hidden on a narrow screen because the phone shell replaces them -- but a visitor
     who chose the desktop from the phone view needs them back, or they get a taskbar and nothing
     else. fitRect already guarantees a window fits whatever screen it opens on. */
  body:not(.force-desktop) .win { display:none; }
  .grid { grid-template-columns:1fr; }
  .timeline li { grid-template-columns:70px 1fr; } .timeline small { grid-column:2; }
  .about-head { flex-direction:column; }
  .detail-ext-header { flex-direction:column; }
}

/* ---- simple view (any width) ---- */
body.is-simple { overflow:auto; background:var(--os-bg) !important; }
body.is-simple #wallpaper-motion, body.is-simple #mist { display:none !important; animation:none !important; }
.simple { max-width:900px; margin:0 auto; padding:24px 16px 64px; }
.simple section { margin-top:40px; }
.s-head { display:flex; align-items:center; justify-content:space-between; gap:16px; border-bottom:1px solid var(--os-border); padding-bottom:16px; flex-wrap:wrap; }
.s-head h1 { margin:0; font-size:24px; }
.s-back { display:inline-flex; align-items:center; justify-content:center; padding:8px 16px; border-radius:8px; background:var(--os-forest); color:var(--os-text); font-weight:600; text-decoration:none; font-size:13px; border:1px solid var(--os-border); transition:background 0.15s, color 0.15s; }
.s-back:hover { background:var(--os-accent); color:var(--os-bg); border-color:var(--os-accent); }
.s-foot { margin-top: 48px; padding: 16px 0; border-top: 1px solid var(--os-border); color: var(--os-muted); font-size: 13px; }

/* ---- desktop watermark / build stamp ---- */
#build-stamp { position: absolute; right: 16px; bottom: 12px; font: 12px var(--os-mono); color: var(--os-muted); opacity: .7; pointer-events: none; user-select: none; z-index: 0; }
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  #build-stamp { display: none !important; }
}

/* Settings was one long undifferentiated scroll. Three labelled groups — Appearance, Desktop,
   Preferences — with every existing control kept, just ordered. Plain headings rather than tabs:
   a tab strip would hide two thirds of the options behind a click on a narrow window. */
.set-group { font-size:15px; margin:28px 0 4px; padding-bottom:6px; letter-spacing:0; text-transform:none;
  color:var(--os-text); border-bottom:1px solid var(--os-border); }
.settings > .set-group:first-child { margin-top:4px; }

/* The brief allows a card to lift 2px on hover. Gated on a hovering pointer so a touch device never
   depends on hover to reveal anything, and on reduced-motion. transform/opacity only. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card { transition: transform var(--t-fast) ease, border-color var(--t-fast) ease; }
  .card:hover { transform: translateY(-2px); border-color: var(--os-border-active); }
}

/* ---- Opening selection: simple portfolio cards, welcome rows, introduction ----
   Image and text sit side by side when there is room and stack when there is not; the picture is
   decoration, so every way into the work is a visible, labelled link. */
.selected-work { margin-top:32px; }
.selected-work > h2 { margin:0 0 14px; font-size:20px; }
.sel-grid { display:grid; grid-template-columns:1fr; gap:18px; }
.sel-card { display:grid; grid-template-columns:minmax(0, 260px) minmax(0, 1fr); gap:20px; padding:16px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--r-lg); }
.sel-card.is-pro { grid-template-columns:88px minmax(0, 1fr); }
.sel-shot { width:100%; aspect-ratio:16/10; object-fit:cover; border-radius:var(--r-sm); background:var(--os-forest); align-self:start; }
.sel-glyph { display:flex; align-items:center; justify-content:center; width:88px; height:88px; border-radius:var(--r-sm);
  background:var(--os-forest); border:1px solid var(--os-border); color:var(--os-accent); }
.sel-glyph .i { width:44px; height:44px; }
.sel-body { min-width:0; }
.sel-kind { margin:0 0 4px; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--os-muted); }
.sel-card.is-pro .sel-kind { color:var(--os-accent); }
.sel-title { margin:0 0 6px; font-size:19px; line-height:1.25; }
.sel-title a { color:inherit; text-decoration:none; }
.sel-title a:hover { text-decoration:underline; text-underline-offset:3px; }
.sel-title a:focus-visible { outline:var(--os-focus); outline-offset:3px; border-radius:2px; }
.sel-sum { margin:0; font-size:14px; line-height:1.5; color:var(--os-muted); }
.sel-line { margin:8px 0 0; font-size:13px; line-height:1.45; color:var(--os-muted); }
.sel-line b { color:var(--os-text); font-weight:600; margin-right:6px; }
.sel-tech, .card-tech { display:flex; flex-wrap:wrap; gap:6px; list-style:none; margin:10px 0 0; padding:0; }
.sel-tech li, .card-tech li { font:11.5px var(--os-mono, monospace); padding:2px 9px; border:1px solid var(--os-border); border-radius:999px; color:var(--os-muted); }
.sel-actions { display:flex; flex-wrap:wrap; gap:8px; margin:14px 0 0; }
@media (max-width: 640px) {
  .sel-card, .sel-card.is-pro { grid-template-columns:1fr; gap:14px; }
  .sel-glyph { width:56px; height:56px; } .sel-glyph .i { width:26px; height:26px; }
}
.card-line { margin:6px 0 0; font-size:12.5px; line-height:1.4; color:var(--os-muted); }
.card-line b { color:var(--os-text); font-weight:600; margin-right:6px; }
.kind-badge { display:inline-block; margin:0 0 8px; padding:2px 10px; border:1px solid var(--os-border); border-radius:999px;
  font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--os-accent); }
.work-note { margin:0 0 12px; }
/* Abstract per-project symbol: professional work has no screenshot to show, and gets no fake one. */
.work-ico { width:20px; height:20px; margin-right:8px; vertical-align:-4px; color:var(--os-accent); }
.work-ico-lg { width:32px; height:32px; vertical-align:-7px; margin-right:12px; }
.s-intro { display:flex; gap:16px; align-items:center; margin:8px 0 4px; }
.s-avatar { width:72px; height:72px; border-radius:50%; object-fit:cover; border:2px solid var(--os-border); flex:none; }
.s-intro h1 { margin:0; font-size:28px; line-height:1.15; }
.s-role { margin:2px 0 0; font-weight:600; }
.s-position { margin:6px 0 10px; color:var(--os-muted); max-width:60ch; line-height:1.5; }
.s-jump { display:flex; flex-wrap:wrap; gap:8px; }
.s-jump .chip { display:inline-flex; align-items:center; min-height:44px; padding:0 16px; text-decoration:none; }
.s-lead { margin:0 0 12px; font-size:13px; color:var(--os-muted); }
.s-brand { margin:0; font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:var(--os-muted); }
@media (max-width: 480px) { .s-intro { align-items:flex-start; } .s-avatar { width:56px; height:56px; } .s-intro h1 { font-size:24px; } }

/* ---- screenshot gallery ---- */
.gallery { margin:16px 0; display:flex; flex-direction:column; gap:10px; }
/* `aspect-ratio` reserves the box BEFORE the file arrives. Without it the image had no width or
   height attributes and no ratio, so it measured 0px tall until it decoded: the panel sat empty,
   then the picture popped in and shoved the page down. Screenshots are captured at 1280x800, so
   16/10 is the real ratio and `contain` keeps anything odd-sized letterboxed rather than cropped. */
.gallery .shot-main { width:100%; aspect-ratio:16/10; max-height:420px; object-fit:contain;
  background:var(--os-forest); border:1px solid var(--os-border); border-radius:var(--r-md); display:block; }

/* Something has to say "this is coming". A quiet sweep across the reserved box, stopped for anyone
   who asked for less motion — they get a plain tint, which still reads as "not ready yet". */
.gallery .shot-main.is-loading, .lb-img.is-loading {
  background-image:linear-gradient(100deg, var(--os-forest) 38%, var(--os-panel) 50%, var(--os-forest) 62%);
  background-size:220% 100%;
  animation:shot-shimmer 1.1s ease-in-out infinite;
}
@keyframes shot-shimmer { from { background-position:150% 0; } to { background-position:-50% 0; } }
@media (prefers-reduced-motion: reduce) {
  .gallery .shot-main.is-loading, .lb-img.is-loading { animation:none; background-image:none; background-color:var(--os-panel); }
}
.lb-img.is-loading { min-width:min(60vw, 600px); min-height:min(40vh, 380px); border-radius:var(--r-md); }
.shot-thumbs { display:flex; gap:8px; flex-wrap:wrap; }
.shot-thumb { width:72px; height:48px; min-width:0; min-height:0; padding:0; border:1px solid var(--os-border);
  border-radius:var(--r-sm); overflow:hidden; background:var(--os-forest); transition:border-color var(--t-fast); }
.shot-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.shot-thumb.is-on { border-color:var(--os-accent); }
.gallery figcaption { margin:0; }

/* ---- lightbox ---- */
.lightbox { position:fixed; inset:0; z-index:3000; display:flex; flex-direction:column;
  background:rgba(8,12,11,.92); padding:16px; gap:12px; }
.lightbox[hidden] { display:none; }
.lb-stage { flex:1; min-height:0; display:flex; align-items:center; justify-content:center; position:relative; }
/* object-fit:contain — the brief is explicit that an enlarged view must not crop product UI. */
.lb-img { max-width:100%; max-height:100%; object-fit:contain; border-radius:var(--r-md); }
/* One line under the picture: what it is, who issued it, when. A certificate viewed full size
   with no caption is just a JPEG. */
.lb-cap { margin:10px auto 0; max-width:min(90vw, 820px); text-align:center; font-size:13px; color:var(--os-text); }
.lb-cap[hidden] { display:none; }
/* The tile is a button now, so it needs to stop looking like one. */
button.cert-media { padding:0; border:0; background:none; cursor:zoom-in; display:block; width:100%; }
button.cert-media:focus-visible { outline:var(--os-focus); outline-offset:2px; }
.lb-nav { position:absolute; top:50%; transform:translateY(-50%); width:44px; height:44px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:50%; font-size:22px; line-height:1; }
.lb-prev { left:8px; } .lb-next { right:8px; }
.lb-bar { display:flex; align-items:center; justify-content:center; gap:16px; color:var(--os-muted); font-size:13px; }
.lb-close { border:1px solid var(--os-border); border-radius:var(--r-sm); padding:0 16px; background:var(--os-panel); }

/* ---- collapsible timeline in About ---- */
.timeline-details > summary {
  display:flex; align-items:baseline; gap:10px; cursor:pointer; list-style:none;
  border-radius:var(--r-sm); padding:2px 0;
}
.timeline-details > summary::-webkit-details-marker { display:none; }
.timeline-details > summary h3 { display:inline; margin:0; }
.timeline-details > summary::after { content:'▸'; color:var(--os-muted); font-size:12px; transition:transform var(--t-fast); }
.timeline-details[open] > summary::after { transform:rotate(90deg); }
.timeline-details > summary:focus-visible { outline:2px solid var(--os-accent); outline-offset:2px; }
.td-count { font-size:12px; color:var(--os-muted); }

/* ---- selected work on the welcome screen: three compact rows, nothing that delays the buttons ---- */
.w-brand { margin:0; font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:var(--os-muted); }
.welcome-box .w-position { font-size:14px; line-height:1.5; max-width:44ch; }
.w-selected { width:100%; text-align:left; border-top:1px solid var(--os-border); padding-top:12px; }
.w-selected h2 { margin:0 0 4px; font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--os-muted); font-weight:600; }
.w-selected ul { list-style:none; margin:0; padding:0; }
.sel-row { display:flex; align-items:baseline; gap:12px; min-height:44px; padding:6px 10px; margin:0 -10px;
  border-radius:var(--r-sm); color:var(--os-text); text-decoration:none; }
.sel-row:hover, .sel-row:focus-visible { background:var(--os-forest); }
.sel-row:focus-visible { outline:var(--os-focus); outline-offset:-2px; }
.sel-row-kind { flex:0 0 142px; font-size:10.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--os-muted); }
@media (max-width: 480px) { .sel-row { flex-direction:column; align-items:flex-start; gap:1px; } .sel-row-kind { flex:none; } }
.sel-row-title { font-size:14px; font-weight:600; }

/* ---- desktop branding ---- */
.desk-brand {
  position: absolute;
  right: 48px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  text-shadow: 0 2px 8px rgba(0,0,0,0.8), 0 1px 3px rgba(0,0,0,0.9);
  /* Light wallpapers (Arctic Porcelain) put pale text on a pale image and the shadow alone is not
     enough — measured unreadable at 1280x800. A soft local scrim, the same treatment the quote
     already uses, so the photograph is not flattened by a full overlay. */
  padding: 14px 18px 16px;
  border-radius: var(--r-lg);
  background: radial-gradient(130% 130% at 75% 50%, rgba(9,14,12,.52), rgba(9,14,12,0) 72%);
}
.desk-brand-name {
  font-size: 34px;
  font-weight: 300;
  color: #f2f6f3;
  text-transform: uppercase;
  letter-spacing: .34em;
  margin-bottom: 14px;
  line-height: 1;
  margin-right: -.34em; /* trailing letter-space would push the block off the right edge */
}
.desk-brand-os { color: var(--os-accent); margin-left: .34em; }
.desk-brand-slogan {
  font-size: 11px;
  font-weight: 400;
  color: var(--os-muted);
  text-transform: uppercase;
  letter-spacing: .42em;
  line-height: 1.2;
  margin-right: -.42em;
}
.desk-brand-slogan i { color: var(--os-accent); font-style: normal; margin: 0 .2em; }

/* ---- desktop About card ---- */
/* Sits under the wordmark in the right-hand identity stack: this is a portfolio, so the desktop
   should say whose it is before a visitor opens anything. Deliberately quieter than the wordmark —
   one avatar, a name, one sentence and one link. It is the only desktop widget that is
   interactive, so unlike .desk-brand/.desk-quote it must NOT set pointer-events: none. */
.desk-about {
  position: absolute;
  right: 48px;
  top: calc(50% + 84px);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 320px;
  padding: 10px 14px;
  border-radius: var(--r-md);
  background: radial-gradient(120% 120% at 70% 40%, rgba(9,14,12,.62), rgba(9,14,12,.12) 78%);
  text-shadow: 0 2px 8px rgba(0,0,0,.85);
}
.desk-about .da-avatar { border-radius: 50%; flex-shrink: 0; object-fit: cover; border: 1px solid var(--os-border-active); }
.desk-about .da-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; text-align: left; }
.desk-about .da-name { font-size: 14px; font-weight: 600; color: var(--os-text); }
.desk-about .da-role { font-size: 11.5px; line-height: 1.35; color: #cbd6cc; }
.desk-about .da-link { font-size: 12px; color: var(--os-accent); text-decoration: none; margin-top: 2px; width: fit-content; }
.desk-about .da-link:hover, .desk-about .da-link:focus-visible { text-decoration: underline; }

/* Short viewports would stack the card into the quote — the wordmark alone carries the identity
   there, and About is one tap away on the desktop and in the launcher. */
@media (max-height: 620px) { .desk-about { display: none !important; } }

/* ---- quote widget ---- */
.desk-quote {
  position: absolute;
  max-width: 300px;
  right: 48px;
  bottom: calc(var(--os-taskbar, 48px) + 28px);
  font-size: 11px;
  color: var(--os-muted);
  text-transform: uppercase;
  line-height: 2.1;
  letter-spacing: .36em;
  margin-right: -.36em;
  color: #dde6dd;
  padding: 10px 14px 12px;
  border-radius: 10px;
  background: radial-gradient(120% 120% at 70% 40%, rgba(9,14,12,.62), rgba(9,14,12,0) 74%);
  text-shadow: 0 2px 8px rgba(0,0,0,0.8);
  pointer-events: none;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
}
.desk-quote::after {
  content: '';
  display: block;
  width: 25px;
  height: 2px;
  background: var(--os-accent);
  margin-top: 14px;
}

/* Hide branding on mobile */
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  .desk-brand { display: none !important; }
  .desk-about { display: none !important; }
}

/* Hide the quote only when the desktop is genuinely short or narrow (899px hid it on nearly
   every laptop window, so nobody ever saw it). */
@media (max-height: 640px), (max-width: 900px) {
  .desk-quote { display: none !important; }
}

/* Hide in simple view */
body.is-simple .desk-brand,
body.is-simple .desk-quote,
html.js body.is-simple #brand,
html.js body.is-simple #quote {
  display: none !important;
}

/* ---- mobile launcher footer ---- */
.m-foot { font: 12px var(--os-mono); color: var(--os-muted); text-align: center; padding: 16px 0 24px; }

/* ---- boot ---- */
.boot { position:fixed; inset:0; z-index:2000; background:var(--os-bg); display:grid; place-items:center; align-content:center; gap:20px; text-align:center; transition:opacity .25s; }
.boot.is-done { opacity:0; pointer-events:none; }
.boot-mark { width:72px; height:72px; opacity:0; } .boot.is-running .boot-mark { animation:boot-fade .4s forwards; }
.boot-lines { list-style:none; margin:0; padding:0; font:14px var(--os-mono); color:var(--os-muted); min-height:66px; }
.boot-lines li { opacity:0; } .boot.is-running .boot-lines li:nth-child(1) { animation:boot-fade .3s .5s forwards; } .boot.is-running .boot-lines li:nth-child(2) { animation:boot-fade .3s 1s forwards; } .boot.is-running .boot-lines li:nth-child(3) { animation:boot-fade .3s 1.5s forwards; }
.boot-bar { width:200px; height:4px; background:var(--os-border); border-radius:2px; overflow:hidden; } .boot-bar span { display:block; height:100%; width:0; background:var(--os-accent); } .boot.is-running .boot-bar span { animation:boot-grow 2.4s .3s linear forwards; }
.boot-actions { display:flex; gap:24px; }
.boot-actions a { display:inline-flex; align-items:center; justify-content:center; min-height:44px; padding:0 12px; color:var(--os-muted); }
.boot-actions a:hover { color:var(--os-accent); }
.boot-error { max-width:280px; color:var(--os-text); }
.boot-error a { color:var(--os-accent); }
.boot.is-error .boot-mark, .boot.is-error .boot-lines, .boot.is-error .boot-bar, .boot.is-error .boot-actions { display:none; }
@keyframes boot-fade { to { opacity:1; } }
@keyframes boot-grow { to { width:100%; } }
@media (prefers-reduced-motion: reduce) { .boot, .boot * { animation:none !important; transition:none !important; } }

/* ---- context menu ---- */
#ctx {
  position: fixed;
  display: none;
  flex-direction: column;
  padding: 4px 0;
  background: var(--os-panel);
  border: 1px solid var(--os-border);
  border-radius: var(--os-radius);
  box-shadow: 0 12px 40px rgba(0,0,0,.5);
  z-index: 1150;
  min-width: 180px;
  outline: none;
}

.ctx-item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0 16px;
  background: none;
  border: none;
  color: var(--os-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ctx-item:focus-visible { outline: var(--os-focus); outline-offset: -2px; }

.ctx-item:hover,
.ctx-item:focus-visible {
  background: var(--os-forest);
  color: var(--os-text);
}

.ctx-item:disabled {
  color: var(--os-muted);
  cursor: not-allowed;
}

/* A mouse does not need a thumb-sized row. 44px stays for touch (the audience on cheap phones and
   tablets); with a precise pointer the menu is compact, like the one in the OS this imitates. */
@media (hover: hover) and (pointer: fine) {
  #ctx { min-width: 200px; padding: 5px; border-radius: 8px; }
  .ctx-item { min-height: 30px; padding: 0 12px; font-size: 13px; line-height: 1.2; border-radius: 5px; }
  #ctx .ctx-sep { margin: 4px 6px; }
}

.ctx-sep {
  height: 1px;
  border: none;
  background: var(--os-border);
  margin: 4px 0;
  padding: 0;
}

/* My PC: the window itself is the responsive container. */
.win[data-id="mypc"] .win-body { padding:0; }
.app-mypc { height:100%; container-type:inline-size; }
.explorer { display:grid; grid-template-columns:200px minmax(0,1fr) 260px; height:100%; min-height:360px; background:var(--os-panel); }
.explorer-sidebar { padding:8px; border-right:1px solid var(--os-border); }
.explorer-sidebar button { display:flex; align-items:center; justify-content:space-between; gap:8px; width:100%; text-align:left; padding:8px; border-radius:var(--os-radius); }
.explorer-sidebar span { color:var(--os-muted); }
.explorer button[aria-current="page"], .explorer button[aria-selected="true"], .explorer button[aria-pressed="true"] { background:var(--os-forest); outline:1px solid var(--os-accent); outline-offset:-1px; }
.explorer button:disabled { color:var(--os-muted); cursor:default; }
.explorer-work { min-width:0; min-height:0; display:flex; flex-direction:column; }
.explorer-toolbar { display:flex; align-items:center; gap:4px; flex-wrap:wrap; padding:8px; border-bottom:1px solid var(--os-border); }
.explorer-crumbs { display:flex; align-items:center; flex-wrap:wrap; }
.explorer-toolbar input { flex:1; width:140px; min-width:100px; min-height:44px; padding:8px; color:var(--os-text); background:var(--os-bg); border:1px solid var(--os-border); border-radius:var(--os-radius); font:inherit; }
.explorer-views { display:flex; gap:4px; }
/* `grid-auto-rows: min-content` is load-bearing, and the reason is not obvious.
   `.explorer-entry` is a <button> with `overflow:hidden`, which makes it a scroll container — and
   a scroll container's grid `min-height:auto` resolves to 0 instead of its content size. So the
   rows were free to shrink to share the container height instead of overflowing it: measured
   `grid-template-rows: 114.5px` for 154px of content, and `overflow:hidden` then clipped the
   folder NAME and item count clean off every tile in the root view. Eight identical unlabelled
   folders, behind the site's primary call to action. It only bit at some widths, because the
   row/column count decides whether the squeeze is needed at all. */
.explorer-items { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); grid-auto-rows:min-content; gap:12px; padding:var(--os-gap); overflow:auto; align-content:start; flex:1; min-height:100px; }
.explorer-entry { min-width:0; text-align:left; padding:8px; border:1px solid var(--os-border); border-radius:var(--os-radius); overflow:hidden; }
.explorer-media { display:block; aspect-ratio:16/10; overflow:hidden; border-radius:var(--os-radius); background:var(--os-forest); }
.explorer-media > img, .explorer-media > .card-noshot { width:100%; height:100%; min-height:0; object-fit:cover; }
.explorer-name { display:block; overflow-wrap:anywhere; font-weight:600; margin-top:8px; }
.explorer-summary { display:block; color:var(--os-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.explorer-items[data-view="grid"] .explorer-summary, .explorer-items[data-view="grid"] time { display:none; }
.explorer-items[data-root="true"] .explorer-summary { display:block; }
.explorer-items[data-view="list"] { display:flex; flex-direction:column; gap:4px; }
.explorer-items[data-view="list"] .explorer-entry { display:grid; grid-template-columns:44px minmax(0,1fr); column-gap:12px; flex-shrink:0; }
.explorer-items[data-view="list"] .explorer-media { grid-row:1/4; width:44px; height:44px; }
.explorer-items[data-view="list"] .card-noshot img { width:28px; height:28px; }
.explorer-items[data-view="list"] .explorer-name { margin:0; }
.explorer-entry time { grid-column:2; color:var(--os-muted); font-size:12px; }
.explorer-preview { min-width:0; padding:var(--os-gap); border-left:1px solid var(--os-border); overflow:auto; overflow-wrap:anywhere; }
.explorer-status { margin:0; padding:8px 16px; color:var(--os-muted); font-size:12px; }
@container (max-width:1099px) {
  .explorer { grid-template-columns:200px minmax(0,1fr); grid-template-rows:minmax(0,1fr) auto; }
  .explorer-sidebar { grid-row:1/3; }
  .explorer-preview { grid-column:2; max-height:150px; border-left:0; border-top:1px solid var(--os-border); }
  .explorer-preview .explorer-media { display:none; }
  .explorer-preview h3, .explorer-preview p { margin:0 0 8px; }
}
@container (max-width:599px) {
  .explorer { grid-template-columns:minmax(0,1fr); height:auto; }
  .explorer-sidebar { grid-row:auto; display:flex; overflow:auto; border-right:0; border-bottom:1px solid var(--os-border); }
  .explorer-sidebar button { width:auto; flex-shrink:0; }
  .explorer-preview { grid-column:1; max-height:none; }
}
@media (max-width:767px) {
  .explorer-views { display:none; }
  .explorer-items[data-view="grid"] { display:flex; flex-direction:column; }
  .explorer-items .explorer-entry { display:grid; grid-template-columns:44px minmax(0,1fr); column-gap:12px; }
  .explorer-items .explorer-media { display:block; grid-row:1/4; width:44px; height:44px; }
  .explorer-items .card-noshot img { width:28px; height:28px; }
  .explorer-items[data-view="grid"] .explorer-summary, .explorer-items[data-view="grid"] time { display:block; }
}

/* Articles: metadata previews, with preferences scoped to this reader. */
.win-body:has(> .app-articles) { padding:0; overflow:hidden; }
.app-articles { height:100%; container-type:inline-size; }
.reader { display:grid; grid-template-columns:200px 320px minmax(0,1fr); height:100%; min-height:0; background:var(--os-bg); color:var(--os-text); --reader-size:16px; }
.reader-font-S { --reader-size:14px; }
.reader-font-L { --reader-size:18px; }
.reader-theme-paper { --os-bg:#f6f2e9; --os-panel:#ede7db; --os-text:#202d29; --os-muted:#526159; --os-border:#b7bbae; --os-accent:#294e3e; --os-forest:#e0e7d7; }
.reader-sidebar { padding:16px 8px; overflow:auto; border-right:1px solid var(--os-border); background:var(--os-panel); }
.reader-sidebar button { display:flex; justify-content:space-between; gap:12px; width:100%; padding:10px 12px; border-radius:6px; text-align:left; }
.reader [aria-current="page"] { background:var(--os-forest); box-shadow:inset 3px 0 var(--os-accent); }
.reader-list { display:flex; flex-direction:column; min-height:0; min-width:0; border-right:1px solid var(--os-border); }
.reader input { margin:16px; width:calc(100% - 32px); min-height:44px; padding:10px; border:1px solid var(--os-border); border-radius:6px; background:var(--os-panel); color:var(--os-text); font:inherit; }
.reader-rows { overflow:auto; flex:1; }
.reader-row { display:flex; gap:12px; align-items:flex-start; position:relative; width:100%; padding:16px 20px; text-align:left; border-bottom:1px solid var(--os-border); }
/* One thumbnail proportion for every row, so the list reads as a column rather than a ragged edge. */
.rr-thumb { width:84px; height:56px; flex:none; border-radius:var(--r-sm); object-fit:cover; background:var(--os-forest); border:1px solid var(--os-border); }
.rr-thumb-none { display:grid; place-items:center; } .rr-thumb-none .glyph { width:22px; height:22px; }
.rr-text { min-width:0; display:flex; flex-direction:column; gap:3px; }
.rr-meta { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--os-muted); }
.reader-row strong { display:block; font-size:15px; line-height:1.4; }
.reader time { display:block; color:var(--os-muted); font-size:13px; margin:8px 0; }
.reader-summary { display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:var(--os-muted); font-size:13px; }
.reader-mark { position:absolute; right:16px; bottom:42px; color:var(--os-accent); }
.reader-count { margin:0; padding:12px 20px; font-size:13px; color:var(--os-muted); }
.reader-pane { min-width:0; overflow:auto; padding:20px 28px 36px; }
.reader-toolbar, .reader-toolbar > div, .reader-actions { display:flex; gap:8px; flex-wrap:wrap; }
.reader-toolbar { justify-content:space-between; border-bottom:1px solid var(--os-border); padding-bottom:16px; }
.reader-toolbar button { padding:8px 12px; border-radius:6px; }
.reader-toolbar [aria-pressed="true"] { background:var(--os-forest); box-shadow:inset 0 -2px var(--os-accent); }
.reader-document { max-width:70ch; margin:28px auto; font-size:var(--reader-size); }
.reader .reader-document h2 { font:600 2em/1.15 Georgia,serif; overflow-wrap:anywhere; margin:16px 0; }
.reader-eyebrow { text-transform:uppercase; letter-spacing:.14em; font-size:12px; color:var(--os-muted); }
.reader-copy { font-size:1em; line-height:1.8; margin:28px 0; }
.reader .btn { min-height:44px; white-space:normal; }
.reader-theme-paper .btn-primary { background:#294e3e; color:#f6f2e9; }
.reader-back { display:none; }
@container (max-width: 950px) { .reader { grid-template-columns:260px minmax(0,1fr); } .reader-sidebar { display:none; } }
@container (max-width: 540px) { .reader { grid-template-columns:1fr; } .reader-pane { display:none; } .reader.has-selection .reader-list { display:none; } .reader.has-selection .reader-pane { display:block; } .reader-back { display:block; } }
@media (max-width:767px) {
  .m-body:has(.reader) { padding:0; }
  .m-body > .app-articles { height:calc(100dvh - 56px); }
  .reader { grid-template-columns:1fr; }
  .reader-sidebar, .reader-pane { display:none; }
  .reader.has-selection .reader-list { display:none; }
  .reader.has-selection .reader-pane { display:block; }
  .reader-back { display:block; margin-bottom:12px; }
  .reader-pane { padding:16px 20px 32px; }
}
/* The 700px box here used to hold the reader, which Simple View no longer renders: it gets a plain
   list of links instead, and that list must be as tall as its content or it overlaps the sections
   below it -- 22 articles in a 700px box spilled over Work and Music. */
.is-simple .app-simple { height:auto; container-type:normal; }

/* First-party arcade: forest tiles, scoped number colours and scroll on small screens. */
.minesweeper { padding:16px; max-width:560px; margin:auto; }
.ms-toolbar, .ms-meters { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-bottom:12px; }
.ms-meters { font-family:var(--os-mono); color:var(--os-muted); }
.ms-scroll { overflow:auto; padding:4px; }
.ms-board { display:grid; grid-template-columns:repeat(9,var(--ms-cell)); gap:3px; width:max-content; }
.ms-board button { width:var(--ms-cell); height:var(--ms-cell); min-width:var(--ms-cell); min-height:var(--ms-cell); padding:0; border:1px solid var(--os-border); border-radius:5px; background:var(--os-forest); font:bold 18px var(--os-mono); touch-action:pan-x pan-y; user-select:none; -webkit-touch-callout:none; }
.ms-board button[data-shown="true"] { background:var(--os-bg); }
.ms-board button[data-count="1"] { color:#96c9ff; }
.ms-board button[data-count="2"] { color:var(--os-accent); }
.ms-board button[data-count="3"] { color:#ffaaa0; }
.ms-board button[data-count="4"] { color:#c7b0ff; }
.ms-board button[data-count="5"] { color:#ffcc8c; }
.ms-board button[data-count="6"] { color:#80ded4; }
.ms-board button[data-count="7"] { color:#f7a9db; }
.ms-board button[data-count="8"] { color:var(--os-text); }

/* Desktop classic: 2048 */
.game2048 { max-width:408px; margin:auto; }
.g2048-toolbar, .g2048-banner { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:8px; margin-bottom:12px; }
.g2048-toolbar { font-family:var(--os-mono); }
.g2048-banner { padding:8px; background:var(--os-forest); border:1px solid var(--os-accent); border-radius:8px; }
.g2048-board { display:grid; grid-template-columns:repeat(4,minmax(64px,1fr)); gap:6px; padding:6px; min-width:286px; background:var(--os-bg); border-radius:10px; touch-action:none; user-select:none; }
.g2048-cell { display:grid; place-items:center; aspect-ratio:1; min-height:64px; border-radius:6px; background:var(--os-panel); color:var(--os-bg); font:bold clamp(18px,3vw,28px) var(--os-mono); }
.g2048-cell[data-value="2"] { background:var(--os-forest); color:var(--os-text); }
.g2048-cell[data-value="4"] { background:#435b38; color:var(--os-text); }
.g2048-cell[data-value="8"] { background:#789357; }
.g2048-cell[data-value="16"] { background:#89a65f; }
.g2048-cell[data-value="32"] { background:#9bbb69; }
.g2048-cell[data-value="64"] { background:#adcf78; }
.g2048-cell[data-value="128"] { background:var(--os-accent); }
.g2048-cell[data-value="256"] { background:#d1ed96; }
.g2048-cell[data-value="512"] { background:#ddf5a4; }
.g2048-cell[data-value="1024"] { background:#e6fab3; }
.g2048-cell[data-value="2048"], .g2048-cell[data-value="4096"] { background:#f0ffcb; }
.g2048-cell.is-new { animation:g2048-pop 140ms ease-out; }
@keyframes g2048-pop { from { transform:scale(.7); opacity:0; } to { transform:scale(1); opacity:1; } }
@media (prefers-reduced-motion: reduce) { .g2048-cell.is-new { animation:none; } }

/* ---- site-nav ---- */
.site-nav { font-size: 12px; opacity: .7; text-align: center; padding: 12px; }
.site-nav a { margin: 0 8px; color: inherit; text-decoration: underline; }
html.js .site-nav { display: none; }

/* Original wallpaper gallery. Thumbnails remain still for easy comparison. */
.wallpaper-options { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
.wallpaper-btn { display:flex; flex-direction:column; align-items:stretch; padding:5px; text-align:left; overflow:hidden; }
.wallpaper-preview { display:block; width:100%; aspect-ratio:16/9; border-radius:5px; background-color:#111819; }
.wallpaper-label { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 5px; font-size:13px; }
.wallpaper-label small { font-size:10px; font-weight:400; color:var(--os-muted); }
.wallpaper-btn[aria-pressed="true"] { box-shadow:0 0 0 1px var(--os-accent); }
#wallpaper-motion { position:fixed; inset:-4%; z-index:0; pointer-events:none; background-position:center; background-size:cover; }
#wallpaper-motion[data-scene="galaxy"] { animation:galaxy-orbit 90s ease-in-out infinite alternate; }
#wallpaper-motion[data-scene="aurora"] { animation:aurora-breathe 24s ease-in-out infinite alternate; }
@keyframes galaxy-orbit { from { transform:scale(1) rotate(-2deg); } to { transform:scale(1.08) rotate(2deg); } }
@keyframes aurora-breathe { from { transform:scale(1.02) translateX(-1%); opacity:.65; } to { transform:scale(1.06) translateX(1%); opacity:1; } }
@media (prefers-reduced-motion:reduce) { #wallpaper-motion { animation:none !important; } }
@media (max-width:767px) { #wallpaper-motion { animation:none !important; } }
@media (max-width:380px) { .wallpaper-label { flex-direction:column; align-items:flex-start; gap:2px; } }

/* ---- work ---- */
.work-history h3 { margin: 24px 0 12px; font-size: 16px; border-bottom: 1px solid var(--os-border); padding-bottom: 6px; }
.work-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 24px; }
.work-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 16px; background: var(--os-bg); border: 1px solid var(--os-border); border-radius: 8px; cursor: pointer; transition: border-color .15s, background-color .15s; }
.work-row:hover { border-color: var(--os-accent); background: rgba(197,229,138,0.05); }
.work-row-meta { display: flex; flex-direction: column; gap: 2px; }
.work-period { font-size: 13px; color: var(--os-muted); }
.work-title { font-size: 15px; color: var(--os-text); }
.work-role-domain { font-size: 13px; color: var(--os-muted); }
.work-row-stack { display: flex; flex-wrap: wrap; gap: 6px; }
.work-row-stack .chip { padding: 2px 8px; font-size: 11px; }
.impact-line { margin-top: 16px; padding: 12px; background: rgba(197,229,138,0.05); border-left: 3px solid var(--os-accent); border-radius: 0 8px 8px 0; font-size: 14px; }
.work-link-wrapper { margin-top: 16px; }

/* ---- certifications ---- */
.cert-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; list-style: none; padding: 0; margin: 24px 0; }
.cert { display: flex; flex-direction: column; background: var(--os-bg); border: 1px solid var(--os-border); border-radius: 8px; overflow: hidden; }
.cert-media { display: flex; align-items: center; justify-content: center; aspect-ratio: 16/11; background: var(--os-panel); border-bottom: 1px solid var(--os-border); overflow: hidden; text-decoration: none; }
.cert-media img { width: 100%; height: 100%; object-fit: contain; }
.cert-issuer-mark { font-size: 13px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.1em; color: var(--os-muted); text-align: center; padding: 12px; }
.cert-media { position: relative; }
.cert-new { position: absolute; top: 8px; left: 8px; padding: 2px 8px; border-radius: 999px; background: var(--os-accent); color: var(--os-bg); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.cert-dates { font-size: 12px; }
.cert-tags, .cert-skills { display: flex; flex-wrap: wrap; gap: 4px; }
.tag { padding: 2px 8px; border-radius: 4px; background: color-mix(in srgb, var(--os-accent) 18%, transparent); color: var(--os-accent); font-size: 11px; font-weight: 600; letter-spacing: .02em; }
.cert-about { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--os-muted); }
.cert-skills .chip { padding: 2px 8px; font-size: 11px; }
.cert-body { display: flex; flex-direction: column; gap: 6px; padding: 12px; flex: 1; justify-content: space-between; }
.cert-body strong { font-size: 14px; color: var(--os-text); line-height: 1.3; }
.cert-body .muted { font-size: 12px; color: var(--os-muted); }
.cert-body .btn-small { align-self: flex-start; margin-top: auto; min-height: 28px; padding: 4px 10px; font-size: 12px; }

/* ---- article document ---- */
.doc {
  max-width: 68ch;
  margin: 28px auto;
  line-height: 1.7;
}
.doc-header {
  margin-bottom: 28px;
}
.doc-cover {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 20px;
}
.doc-header h1 {
  font-size: 2rem;
  line-height: 1.2;
  margin: 16px 0 8px;
}
.doc-meta {
  font-size: 13px;
  color: var(--os-muted);
  margin-bottom: 24px;
}
.doc-body h2 {
  font-size: 1.25rem;
  margin-top: 1.8em;
  margin-bottom: 12px;
}
.doc-body p {
  margin-bottom: 1.5em;
}
.doc-body blockquote {
  border-left: 3px solid var(--os-accent);
  font-style: italic;
  padding-left: 16px;
  margin: 24px 0;
  color: var(--os-muted);
}
.doc-body figure {
  margin: 24px 0;
  width: 100%;
}
.doc-body figure img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 8px;
}
.doc-body figcaption {
  font-size: 12px;
  color: var(--os-muted);
  margin-top: 8px;
  text-align: center;
}
.doc-body ul {
  padding-left: 20px;
  margin-bottom: 1.5em;
}
.doc-body li {
  margin-bottom: 0.5em;
}
.doc-footer {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--os-border);
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
@media (max-width: 767px), (pointer: coarse) and (hover: none) and (max-height: 480px) {
  .doc {
    max-width: 100%;
    padding: 0 16px;
    margin: 16px auto;
  }
}

/* Explorer Columns & Headers in list mode */
.explorer-headers {
  display: grid;
  grid-template-columns: 44px 2.5fr 1fr 1.2fr 1fr;
  column-gap: 12px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--os-border);
  background: var(--os-panel);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--os-muted);
}
.explorer-headers button {
  background: none;
  border: none;
  text-align: left;
  font-weight: bold;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--os-muted);
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
}
.explorer-headers button:hover {
  color: var(--os-text);
}
.explorer-items[data-view="list"] .explorer-entry.col-row {
  display: grid;
  grid-template-columns: 44px 2.5fr 1fr 1.2fr 1fr;
  column-gap: 12px;
  align-items: center;
  padding: 8px 16px;
}
.explorer-items[data-view="list"] .explorer-entry.col-row .col-name {
  font-weight: 600;
  color: var(--os-text);
}
.explorer-items[data-view="list"] .explorer-entry.col-row .col-date,
.explorer-items[data-view="list"] .explorer-entry.col-row .col-type,
.explorer-items[data-view="list"] .explorer-entry.col-row .col-size {
  font-size: 13px;
  color: var(--os-muted);
}

/* Paper miniature cards in grid mode */
.explorer-entry.paper {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 0;
  height: 200px;
  background: var(--os-bg);
  border: 1px solid var(--os-border);
  border-radius: var(--os-radius);
  overflow: hidden;
}
.paper-cover-strip {
  width: 100%;
  height: 72px;
  background-size: cover;
  background-position: center;
  flex-shrink: 0;
  border-bottom: 1px solid var(--os-border);
}
.paper-body {
  padding: 10px;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.paper-body .explorer-name {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.35;
  font-weight: bold;
  color: var(--os-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.paper-lines {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: auto;
}
.paper-lines span {
  display: block;
  height: 4px;
  background: color-mix(in srgb, var(--os-muted) 25%, transparent);
  border-radius: 2px;
}
.paper-lines span:nth-child(1) { width: 90%; }
.paper-lines span:nth-child(2) { width: 85%; }
.paper-lines span:nth-child(3) { width: 95%; }
.paper-lines span:nth-child(4) { width: 60%; }
.paper-lines span:nth-child(5) { width: 45%; }

/* ---- welcome screen ---- */
body.is-welcome #desktop, body.is-welcome #taskbar, body.is-welcome #panel {
  display: none !important;
}
/* The card is centred with auto margins, not grid centring: when it is taller than the screen, grid
   centring pushes the top half out of reach and the surface cannot scroll to it, which cut the portrait
   off on a phone and on a short laptop window. Auto margins centre it while it fits and start it at the
   top while it does not, and the surface scrolls. */
.welcome {
  position: fixed;
  inset: 0;
  display: flex;
  z-index: 1050;
  padding: 16px;
  box-sizing: border-box;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.welcome-box {
  margin: auto;
  background: var(--os-panel);
  border: 1px solid var(--os-border);
  border-radius: var(--os-radius);
  padding: 28px 32px;
  max-width: 480px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, .6);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
}
.w-avatar {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--os-border);
}
.w-initials {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--os-forest);
  color: var(--os-text);
  font-weight: bold;
  font-size: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--os-border);
}
.welcome-box h1 {
  margin: 0;
  font-size: 28px;
  font-weight: 700;
  color: var(--os-text);
  line-height: 1.2;
}
.welcome-box p {
  margin: 0;
  font-size: 16px;
  color: var(--os-muted);
}
.welcome-links {
  display: flex;
  gap: 16px;
  width: 100%;
  justify-content: center;
  margin-top: 8px;
}
.welcome-links .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 24px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  flex: 1;
  box-sizing: border-box;
}
.welcome-links .btn-primary {
  background: var(--os-accent);
  color: var(--os-bg);
  border: 1px solid var(--os-accent);
}
.welcome-links .btn-primary:hover {
  background: color-mix(in srgb, var(--os-accent) 85%, white);
}
.welcome-links .btn:not(.btn-primary) {
  background: none;
  border: 1px solid var(--os-border);
  color: var(--os-text);
}
.welcome-links .btn:not(.btn-primary):hover {
  background: var(--os-forest);
}
@media (max-height: 760px) {
  .welcome-box { padding: 24px; gap: 14px; }
  .w-avatar, .w-initials { width: 56px; height: 56px; }
  .welcome-box h1 { font-size: 24px; }
}
@media (max-width: 480px) {
  .welcome-box {
    padding: 24px 16px;
  }
  .welcome-links {
    flex-direction: column;
    gap: 12px;
  }
}

/* ---- shortcuts help ---- */
.shortcuts-help {
  padding: 20px;
  overflow-y: auto;
  height: 100%;
  box-sizing: border-box;
}
.shortcuts-help h2 {
  margin: 0 0 16px;
  font-size: 18px;
  border-bottom: 1px solid var(--os-border);
  padding-bottom: 8px;
}
.shortcuts-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px 24px;
  align-items: center;
  margin: 0;
  padding: 0;
}
.shortcuts-list dt {
  grid-column: 1;
  display: flex;
  gap: 4px;
  align-items: center;
}
.shortcuts-list dd {
  grid-column: 2;
  margin: 0;
  font-size: 14px;
  color: var(--os-muted);
}

/* Taskbar Peek Preview Card */
.peek-card {
  background: var(--os-panel);
  border: 1px solid var(--os-border);
  border-radius: var(--os-radius);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
}

.peek-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  background: rgba(255, 255, 255, 0.05);
  border-bottom: 1px solid var(--os-border);
  font-size: 12px;
  flex-shrink: 0;
  height: 24px;
  box-sizing: border-box;
}

.peek-header img {
  width: 14px;
  height: 14px;
}

.peek-title {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
  color: var(--os-text);
}

.peek-close {
  background: none;
  border: none;
  color: var(--os-muted);
  font-size: 14px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.peek-close:hover {
  color: var(--os-accent);
}

.peek-fallback {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px;
  text-align: center;
  box-sizing: border-box;
}

.peek-fallback img {
  width: 32px;
  height: 32px;
}

.peek-fallback span {
  font-size: 11px;
  color: var(--os-muted);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}


/* Wallpaper credit is independent of the UI palette and remains readable on photos. */
#wallpaper-credit {
  position:fixed; bottom:68px; left:50%; transform:translateX(-50%);
  z-index:1; max-width:50vw; padding:5px 10px; border-radius:6px;
  background:rgba(10,18,17,.9); color:#f1f5ef; font-size:11px; text-align:center;
}
body.is-simple #wallpaper-credit { display:none !important; }
@media (max-width:767px) { #wallpaper-credit { display:none !important; } }

/* ---- phone view <-> desktop view ---- */
/* The invitation sits in the phone shell; the way back sits in the app menu and appears only for
   a visitor who took the invitation. Neither is shown to anyone who did not ask. */
.m-desk { margin:0 16px 16px; padding:14px 16px; border:1px solid var(--os-border); border-radius:var(--os-radius); background:var(--os-panel); }
.m-desk h3 { margin:0 0 6px; font-size:15px; }
.m-desk p { margin:0; font-size:13px; line-height:1.55; color:var(--os-muted); }
.m-desk-actions { margin-top:12px !important; }
.m-desk .btn { min-height:44px; }
.menu-foot-phone { display:none; text-align:left; }
body.force-desktop .menu-foot-phone { display:flex; flex-direction:column; }
/* A 375px taskbar has no room for the wordmark beside five pinned apps -- it wrapped to two lines. */
@media (max-width: 480px) { body.force-desktop .apps-btn-word { display:none; } }

/* ---- settings: touch targets ---- */
/* Measured on an iPad-sized viewport: these three toggle rows had a 23px tap height and the
   opacity slider 16px, while every other control in the OS is 44. A 13px native checkbox is hard
   to hit with a finger, and the label is what a person actually aims at -- so the LABEL carries
   the height, and the box grows with it. */
.set-check-row { margin:0; }
.set-check {
  display:flex; align-items:center; gap:10px; cursor:pointer;
  min-height:44px; padding:0 4px; border-radius:var(--r-sm, 8px);
}
.set-check:hover { background:var(--os-forest); }
.set-check input[type="checkbox"] { width:18px; height:18px; flex:none; accent-color:var(--os-accent); }
/* A range input's own box is the track; the thumb is what a finger chases. Give the control a
   44px row to live in without making the track itself look fat. */
.set-range { display:flex; align-items:center; gap:10px; min-height:44px; }
.set-range input[type="range"] { height:44px; accent-color:var(--os-accent); background:transparent; }
.set-range input[type="range"]::-webkit-slider-runnable-track { height:6px; border-radius:3px; background:var(--os-border); }
.set-range input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:22px; height:22px; margin-top:-8px; border-radius:50%; background:var(--os-accent); }
.set-range input[type="range"]::-moz-range-track { height:6px; border-radius:3px; background:var(--os-border); }
.set-range input[type="range"]::-moz-range-thumb { width:22px; height:22px; border:0; border-radius:50%; background:var(--os-accent); }

/* ---- simple view: plain lists ---- */
/* Real <a href> per item, because this is the page a crawler and a visitor on a bad connection
   get. The reader's 35 buttons carried no link at all. */
.s-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:20px; }
.s-list > li { padding-bottom:18px; border-bottom:1px solid var(--os-border); }
.s-list > li:last-child { border-bottom:0; }
.s-list h3 { margin:0; font-size:17px; line-height:1.35; }
.s-list a { color:inherit; text-decoration:none; }
.s-list a:hover h3 { color:var(--os-accent); text-decoration:underline; }
.s-list .s-meta { margin:4px 0 0; font-size:12px; color:var(--os-muted); }
.s-list .s-sum { margin:8px 0 0; line-height:1.6; max-width:70ch; }
.s-list .s-orig { margin:8px 0 0; font-size:13px; }
.s-list .s-orig a { color:var(--os-accent); text-decoration:underline; }
.s-list-music > li { padding-bottom:12px; }
.s-list-music h3 { font-size:15px; font-weight:500; }

/* ---- welcome screen ---- */
.w-who { margin:0 0 6px; font-size:14px; line-height:1.5; color:var(--os-muted); max-width:46ch; }
.w-who strong { color:var(--os-text); font-weight:600; }
.intro-startup, .w-startup { font-size:13px; color:var(--os-muted); }
.intro-startup { margin-top:2px; }
.w-startup { justify-content:center; margin-top:4px; }

/* ---- guided tour ---- */
/* The dim is painted by the ring's own huge shadow, so the highlighted control stays fully lit and
   nothing sits on top of it -- no second overlay to keep in sync with the cut-out. */
.tour { position:fixed; inset:0; z-index:2500; pointer-events:none; }
.tour-card { pointer-events:auto; }
.tour-ring {
  position:fixed; border-radius:10px; pointer-events:none;
  border:2px solid var(--os-accent);
  box-shadow: 0 0 0 9999px rgba(4, 8, 7, .62), 0 0 0 4px rgba(197, 229, 138, .25);
  transition: left .18s ease, top .18s ease, width .18s ease, height .18s ease;
}
@media (prefers-reduced-motion: reduce) { .tour-ring { transition:none; } }
.tour-card {
  position:fixed; width:280px; max-width:calc(100vw - 16px); box-sizing:border-box;
  padding:14px 16px 12px; background:var(--os-panel);
  border:1px solid var(--os-border); border-radius:var(--os-radius);
  box-shadow:0 16px 48px rgba(0,0,0,.55);
}
.tour-card h3 { margin:2px 0 6px; font-size:16px; }
.tour-step { margin:0; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--os-accent); }
.tour-text { margin:0; font-size:13px; line-height:1.55; color:var(--os-muted); }
.tour-actions { display:flex; gap:8px; margin-top:14px; }
.tour-actions .btn { min-height:44px; padding:0 14px; font-size:13px; }
.tour-actions .btn-primary { margin-left:auto; }
.cert-count { position:absolute; left:6px; bottom:6px; padding:2px 7px; border-radius:999px; font-size:10px; background:rgba(4,8,7,.78); color:var(--os-text); border:1px solid var(--os-border); }
.cert-credit { font-size:12px; }
.cert-people { margin:8px 0 0; font-size:12px; color:var(--os-muted); }
.cert-people a { color:var(--os-accent); }

/* ---- taskbar on a phone in the desktop view ---- */
/* Measured at 375px: the bar needed 399px, so #settings-btn ended 24px off the right edge and
   #tasks was squeezed to ZERO width -- the open-window button had nowhere to live, which is also
   why the tour could not find it. Five pinned launchers were taking 191px of a 375px bar.
   The tray is the part that must never be pushed off (clock, show desktop, Settings), and the task
   list is how you get back to a window, so both keep their space; the pinned row shrinks and
   scrolls instead. Buttons stay 44px -- this is a touch device, and that is the whole point. */
@media (max-width: 480px) {
  body.force-desktop .taskbar { gap: 2px; padding: 0 4px; }
  body.force-desktop #pinned {
    min-width: 44px; flex: 0 1 auto; overflow-x: auto; scrollbar-width: none;
    overscroll-behavior-x: contain;
  }
  body.force-desktop #pinned::-webkit-scrollbar { display: none; }
  body.force-desktop #tasks {
    min-width: 44px; flex: 1 1 auto; overflow-x: auto; scrollbar-width: none;
  }
  body.force-desktop #tasks::-webkit-scrollbar { display: none; }
  /* A task button is a glyph plus a title; at this width the TITLE is what has to give. Hiding
     every span took the icon with it and left an empty box. */
  body.force-desktop #tasks .task span:not(.glyph) { display: none; }
  /* The clock carries min-width:90px for the desktop, but the flex squeeze shrank its BUTTON to
     56px while the clock kept its 90 -- so it spilled 34px outside the button and sat against the
     gear. Measured content is 53px (time) and 38px (date), so it needs about 65 with padding. */
  body.force-desktop .clock-btn { flex: none; }
  body.force-desktop .clock { min-width: 0; padding: 0 6px; }
  body.force-desktop #tasks .task { width: 44px; padding: 0; justify-content: center; }
}

/* ---- "Show me around", before the tour has been taken ---- */
/* The two buttons beside it are a destination and a contact page; this one explains the desktop
   itself, which is the thing a first-time visitor does not know to ask for. It carries the accent
   edge and a slow breath until the tour is done once, then goes quiet for good. */
.tour-cta.is-nudge {
  border-color: var(--os-accent);
  color: var(--os-text);
  box-shadow: 0 0 0 0 rgba(197, 229, 138, .45);
  animation: tour-cta-breathe 2.6s ease-out infinite;
}
.tour-cta.is-nudge::before {
  content: "";
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--os-accent);
  flex: none;
}
/* A pointer that is already on the button, or a keyboard that has focused it, has found it --
   stop asking for attention. */
.tour-cta.is-nudge:hover, .tour-cta.is-nudge:focus-visible { animation: none; box-shadow: none; }
@keyframes tour-cta-breathe {
  0%   { box-shadow: 0 0 0 0 rgba(197, 229, 138, .45); }
  70%  { box-shadow: 0 0 0 10px rgba(197, 229, 138, 0); }
  100% { box-shadow: 0 0 0 0 rgba(197, 229, 138, 0); }
}
/* Reduced motion keeps the accent edge and the dot, and drops the pulse. */
@media (prefers-reduced-motion: reduce) {
  .tour-cta.is-nudge { animation: none; box-shadow: none; }
}

/* ---- Settings option lists in a narrow window ---- */
/* Each option is sized to its own label, which reads fine on a desktop where they sit in a
   wrapping row. Stacked in a 341px column they came out 159, 176, 202 and 206 wide -- a list of
   buttons that looks broken rather than deliberate. Measured on a 375px phone in the desktop view. */
@media (max-width: 480px) {
  .theme-btn, .panel-btn, .clock-btn-opt, .player-btn { width: 100%; justify-content: flex-start; }
  .wallpaper-options, .clock-options, .panel-options, .player-options { gap: 8px; }
}

/* ---- terminal quick commands (touch only) ---- */
/* Added by terminal.js only when the pointer is coarse, so a desktop keeps the bare prompt. */
.t-quick { display:flex; gap:6px; overflow-x:auto; scrollbar-width:none; padding:6px 0 2px; flex:none; }
.t-quick::-webkit-scrollbar { display:none; }
.t-quick-btn {
  flex:none; min-height:44px; padding:0 14px; border-radius:var(--r-sm, 8px);
  border:1px solid var(--os-border); background:var(--os-panel);
  color:var(--os-accent); font:inherit; font-size:13px; cursor:pointer;
}
.t-quick-btn:hover, .t-quick-btn:focus-visible { border-color:var(--os-accent); }
.w-copy { margin:14px 0 0; font-size:11px; color:var(--os-muted); }
.m-foot, .s-foot { line-height:1.7; }

/* ---- timeline ---- */
.timeline-details > summary { cursor:pointer; }
/* 24px of text is a fine target for a mouse and a poor one for a finger. */
@media (max-width: 480px), (pointer: coarse) {
  .timeline-details > summary { min-height:44px; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
}
.td-hint { color:var(--os-accent); }
.timeline-details[open] .td-hint::after { content:''; }
/* A side project is a different kind of row from a job, and the dates sit next to each other. */
.tl-tag { display:inline-block; margin-left:8px; padding:1px 8px; border-radius:999px; font-style:normal;
  font-size:11px; color:var(--os-accent); background:var(--os-forest); border:1px solid var(--os-border); white-space:nowrap; }

/* ---- the tour invitation, before it has been taken ---- */
/* It used to be a third outline button in a row whose first button is filled, so the eye never
   reached it. A band with its own edge, mark and sentence is a different object from a button. */
.intro-tour {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:12px 14px; border-radius:var(--r-md, 10px);
  background:var(--os-forest);
  border:1px solid var(--os-accent);
  box-shadow:0 0 0 0 rgba(197, 229, 138, .40);
  animation:tour-cta-breathe 2.8s ease-out infinite;
}
.intro-tour:hover, .intro-tour:focus-within { animation:none; box-shadow:none; }
@media (prefers-reduced-motion: reduce) { .intro-tour { animation:none; box-shadow:none; } }
.intro-tour-mark { width:22px; height:22px; flex:none; color:var(--os-accent); }
.intro-tour-text { display:flex; flex-direction:column; gap:1px; flex:1; min-width:150px; }
.intro-tour-text strong { font-size:14px; }
.intro-tour-text span { font-size:12px; color:var(--os-muted); }
.intro-tour .tour-cta { flex:none; }
/* Inside the band the button carries the invitation, so it does not also need to pulse. */
.intro-tour .tour-cta.is-nudge { animation:none; box-shadow:none; border-color:var(--os-accent); }
.intro-tour .tour-cta.is-nudge::before { display:none; }

/* ---- music in a narrow window ---- */
/* .is-song sizes by HEIGHT with width:auto, which was the fix for the opposite bug: as a
   shrinkable flex item it lost its ratio and came out 598x182, under YouTube's 200px floor. That
   fix never looks at the available WIDTH, so in a 341px column it computed 558x314 -- a video
   wider than the window it sits in. Cap the width here and let min-height hold the floor:
   341 wide at 16/9 is 192 tall, eight pixels short, so 200 wins and the box goes slightly wider
   than 16/9. YouTube letterboxes inside it, which is the right trade at this size. */
@media (max-width: 767px) {
  .music-player-container.is-song, .music-player-container.is-short {
    width: 100%; max-width: 100%; height: auto; min-height: 200px;
  }
  /* The transport is a single 640px row; at 341 it ran 86px past the window. */
  .music-transport { flex-wrap: wrap; max-width: 100%; min-width: 0; row-gap: 8px; }
  .music-transport .mt-seek { flex: 1 1 100%; min-width: 0; }
  .music-transport .mt-vol { flex: 1 1 auto; min-width: 0; }
  .music-transport .mt-volume { width: 100%; }
  /* 40px is the desktop size for these; a finger needs 44 like every other control here. */
  .mt-btn { min-width: 44px; min-height: 44px; }
}
/* The lightbox and calendar arrows are drawn marks now; .i takes the button's own colour. */
.lb-nav .i, .cal-nav .i { width:20px; height:20px; }

/* "Buy me a coffee" is deliberately the quiet one here -- transparent, muted, smaller than the
   three buttons above it. That reads right with a mouse; at 32px it is under the minimum on a
   touch device, where every other control in the OS is 44. Keep the look, grow the target. */
@media (max-width: 767px), (pointer: coarse) {
  .rows.connect .connect-aside .btn { min-height: 44px; padding: 0 12px; }
}

/* ---- taskbar: skip buttons only when the bar can spare the room ---- */
/* The bar already had to be rescued from overflowing at 375px, where five pinned launchers were
   pushing Settings off the edge. Two more 44px controls are a luxury a narrow bar cannot afford,
   so they are the first thing to go and play/pause always stays. */
@media (max-width: 900px) { .np-step { display: none; } }

/* ---- RW Player: the RW Deck skin ---- */
/* A homage to the classic skinned players, drawn rather than borrowed -- the original skins are
   somebody else's copyright. Every surface is mixed from the OS tokens with color-mix, so all four
   themes get the hardware look without a second stylesheet: Copper Dusk reads as warm hi-fi, the
   others change palette and nothing else. */
/* The player has to fill the window body, not merely sit in it: with an auto-height parent the
   inner flex has nothing to divide, so a 200px picture pushed the playlist 167px below the frame
   instead of making the list give up its space. */
/* .win-body is a block, so flex rules on .app do nothing -- these apps have to be given the height
   or they collapse to their content and the editor ends up 200px tall in a 600px window. */
.app-player, .app-editor, .app-files, .app-preview, .app-winamp { height:100%; min-height:0; display:flex; }
.app-player > .rp, .app-editor > .ed, .app-files > .fl, .app-preview > .pv, .app-winamp > .wa { flex:1 1 auto; min-width:0; }
.rp { display:flex; flex-direction:column; gap:12px; height:100%; min-height:0; padding:2px; min-width:0; overflow-y:auto; }
.rp * { box-sizing:border-box; }

/* The screen. Recessed, with a thin bezel and an inner shadow -- depth is what stops it reading as
   a blank div, and it is the one thing on the panel the eye should land on first. */
.rp-screen {
  position:relative; padding:12px 14px 10px;
  background:linear-gradient(180deg, color-mix(in srgb, var(--os-bg) 60%, #000), color-mix(in srgb, var(--os-bg) 80%, #000));
  border:1px solid var(--os-border); border-radius:var(--r-sm, 8px);
  box-shadow:inset 0 3px 12px rgba(0,0,0,.55), 0 1px 0 color-mix(in srgb, var(--os-text) 10%, transparent);
  color:var(--os-accent); display:flex; flex-direction:column; gap:2px; min-width:0;
}
.rp-readout {
  display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0;
  font:10px/1.4 var(--os-mono, monospace); letter-spacing:.09em; text-transform:uppercase;
}
.rp-readout .rp-status { color:var(--os-muted); text-align:right; }
.rp-src { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
.rp-led { width:5px; height:5px; border-radius:50%; background:var(--os-muted); flex:none; }
.rp.is-playing .rp-led { background:var(--os-accent); box-shadow:0 0 6px var(--os-accent); }
.rp-title {
  margin:10px 0 2px; font:14px/1.3 var(--os-mono, monospace); color:var(--os-text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
.rp-title:focus-visible { outline:2px solid var(--os-accent); outline-offset:2px; border-radius:4px; }
/* The big digital clock, in tabular figures so the last digit stops the display twitching. */
.rp-time { margin:0; font:38px/1 var(--os-mono, monospace); letter-spacing:-.03em; font-variant-numeric:tabular-nums; }
.rp-total { font-size:13px; letter-spacing:0; color:var(--os-muted); }

.rp-stage { position:relative; margin:10px 0 8px; min-width:0; }
.rp-vis { display:block; width:100%; height:58px; }
.rp-vis-note {
  margin:0 0 6px; font:11px/1.5 var(--os-mono, monospace);
  color:var(--os-muted); text-align:center; text-wrap:balance;
}
/* Video and YouTube get a real rectangle. Squeezing a picture into the analyser's 58px strip, or
   covering a provider's own controls with a bezel, is not something a skin gets to do.
   The cap matters as much as the ratio: a full 16:9 picture in a 460x560 window measured 282px
   tall, which pushed the transport, the sources and the whole playlist out of the window. */
/* Each source shows ITS OWN element and nothing else. `.rp.has-picture` covered all three, so a
   Spotify embed rendered underneath a YouTube mount that was still on screen: the stage came out
   523px tall with the iframe 1271px down the page, outside the window entirely. */
.rp-video, .rp-yt, .rp .rp-svc, .rp-stage iframe { display:none; width:100%; border:0; }
.rp.has-picture .rp-stage {
  aspect-ratio:var(--rp-ratio, 16/9); background:#000; border-radius:4px; overflow:hidden;
  /* align-self, not margin: a block in a flex column is STRETCHED to the full width, and a stretched
     box makes aspect-ratio compute a height it then has clamped away -- which is how a 9:16 phone
     clip ended up in a 307x176 letterbox with black walls either side. */
  align-self:center; max-width:100%;
}
/* A local video may be capped to keep the transport and the queue in the frame. */
/* Height is the scarce dimension in this window, so a local video is sized by height and lets
   its own ratio choose the width. */
.rp.is-video .rp-stage { height:min(30vh, 176px); width:auto; max-height:none; }
/* YouTube may not. Their required minimum functionality is an embed of at least 200x200, so the
   cap that suits our own files would put us in breach of the terms we play under. It gets its
   200px and the panel scrolls instead. */
.rp.is-youtube .rp-stage { min-height:200px; }
.rp.is-video .rp-video { display:block; height:100%; }
.rp.is-youtube .rp-yt, .rp.is-youtube .rp-stage iframe.rp-yt, .rp.is-youtube .rp-stage iframe:not(.rp-svc) { display:block; height:100%; }
.rp.is-video .rp-video { object-fit:contain; background:#000; }
/* With a picture on screen the clock stops being the hero, or the two fight for the same window. */
.rp.has-picture .rp-time { font-size:20px; }
.rp.has-picture .rp-stage { margin:8px 0 6px; }
.rp.has-picture .rp-title { margin-top:6px; }

.rp-modes {
  display:flex; gap:2px; flex-wrap:wrap; border-top:1px solid color-mix(in srgb, var(--os-border) 70%, transparent);
  padding-top:6px; margin-top:auto;
}
.rp-mode {
  flex:1 1 auto; min-width:44px; min-height:28px; padding:4px 6px;
  background:none; border:1px solid transparent; border-radius:6px; cursor:pointer;
  font:10px var(--os-mono, monospace); letter-spacing:.12em; text-transform:uppercase; color:var(--os-muted);
}
.rp-mode:hover:not(:disabled) { color:var(--os-text); }
.rp-mode[aria-pressed="true"] { color:var(--os-accent); border-color:color-mix(in srgb, var(--os-accent) 45%, transparent); }
.rp-mode:disabled { opacity:.35; cursor:default; }
/* In full screen the stage IS the screen: the bezel, the rounded corner and the height cap all
   belong to a box inside a window, and none of them apply once there is no window. */
.rp-stage:fullscreen { width:100vw; height:100vh; max-height:none; aspect-ratio:auto; border-radius:0; background:#000; }
.rp-stage:fullscreen .rp-video { width:100%; height:100%; }
/* A provider's embed. It keeps its own controls and its own chrome, and nothing of ours sits over
   the top of it. */
.rp-svc { display:none; width:100%; height:100%; border:0; }
/* Spotify's own sizes: a single track is a 152px strip, a playlist/album/artist is a 352px card.
   Left to grow it came out 523px tall and pushed everything else down the panel. */
.rp.is-service .rp-stage { aspect-ratio:auto; height:auto; width:100%; max-width:none; background:transparent; }
.rp.is-service .rp-svc, .rp.is-service .rp-stage iframe.rp-svc { display:block; height:352px; }
/* Same specificity as the rule above it, or the 352 wins and a single track gets a card-sized box. */
.rp.is-service.is-service-track .rp-stage iframe.rp-svc { height:152px; }
.rp.is-link-only .rp-stage { display:none; }
.rp-svc-note { margin:0 0 6px; font-size:11px; line-height:1.5; color:var(--os-muted); }
.rp-svc-note a { color:var(--os-accent); }
/* Cover art in the queue, where there is real art to show. */
.rp-art { display:block; width:32px; height:24px; object-fit:cover; border-radius:3px; }
.rp-row-n:has(.rp-art) { width:32px; }
.np-art-none { width:28px; height:28px; border-radius:4px; background:color-mix(in srgb, var(--os-accent) 22%, transparent); display:inline-block; }
/* Icons with tooltips, not words. Three word-buttons plus four mode labels did not fit one row. */
.rp-icon {
  flex:0 0 auto; display:grid; place-items:center; min-width:32px; min-height:28px; padding:0 6px;
  background:none; border:1px solid var(--os-border); border-radius:6px; color:var(--os-muted); cursor:pointer;
}
.rp-icon:hover { color:var(--os-accent); border-color:var(--os-accent); }
.rp-icon[aria-pressed="true"] { color:var(--os-accent); border-color:var(--os-accent); }
.rp-icon .i { width:16px; height:16px; background:currentColor; mask:var(--i) center/contain no-repeat; -webkit-mask:var(--i) center/contain no-repeat; }

.rp-seek, .rp-volume { accent-color:var(--os-accent); margin-inline:0; }
.rp-seek { width:100%; min-width:0; }

/* Transport. Keys, not toolbar buttons: a lit top edge and a thicker bottom border is the whole
   trick, and it is what makes them feel pressable rather than drawn. */
.rp-controls { display:flex; align-items:center; gap:6px; flex-wrap:wrap; min-width:0; }
.rp-key {
  height:44px; min-width:46px; display:grid; place-items:center; cursor:pointer;
  background:linear-gradient(180deg, color-mix(in srgb, var(--os-panel) 72%, var(--os-text)), var(--os-panel));
  border:1px solid var(--os-border); border-bottom-width:3px; border-radius:7px; color:var(--os-text);
}
.rp-key:hover { border-color:color-mix(in srgb, var(--os-accent) 55%, var(--os-border)); }
.rp-key:active { transform:translateY(1px); border-bottom-width:2px; }
.rp-key .i { width:18px; height:18px; background:currentColor; mask:var(--i) center/contain no-repeat; -webkit-mask:var(--i) center/contain no-repeat; }
.rp-key-main { min-width:58px; background:var(--os-accent); border-color:var(--os-accent); color:var(--os-bg); }
.rp-mute[aria-pressed="true"] { border-color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 18%, var(--os-panel)); color:var(--os-accent); }
.rp-vol-wrap { display:flex; flex-direction:column; gap:2px; flex:1 1 110px; min-width:96px; padding-left:4px; }
.rp-vol-label { font:9px var(--os-mono, monospace); letter-spacing:.12em; text-transform:uppercase; color:var(--os-muted); }
.rp-volume { width:100%; }
.rp.is-muted .rp-vol-wrap { opacity:.5; }

/* Where the tracks come from, kept apart from the transport so the two never get confused. */
.rp-sources { display:flex; gap:6px; flex-wrap:wrap; min-width:0; }
.rp-source {
  flex:1 1 96px; min-height:38px; padding:8px 10px; cursor:pointer;
  background:color-mix(in srgb, var(--os-panel) 80%, transparent); color:inherit;
  border:1px solid var(--os-border); border-radius:6px; font-size:12px; text-align:center;
}
.rp-source:hover { border-color:var(--os-accent); color:var(--os-accent); }
.rp-source[aria-expanded="true"] { border-color:var(--os-accent); color:var(--os-accent); }
.rp-add { display:flex; flex-direction:column; gap:6px; }
.rp-add-label { font-size:11px; color:var(--os-muted); }
.rp-add-row { display:flex; gap:6px; flex-wrap:wrap; }
.rp-url { flex:1 1 160px; min-width:0; }

/* Only this region scrolls. The transport and the sources sit above it and are therefore always
   in the window -- the queue used to be the thing that quietly fell off the bottom. */
/* The queue keeps a floor. Without one it was squeezed to nothing on a 375px window -- screen,
   seek, transport and sources together came to 484 of 486px and the queue simply did not exist. */
.rp-scroll { flex:1 1 auto; min-height:120px; overflow-y:auto; display:flex; flex-direction:column; gap:10px; }
/* The screen keeps its own height. Letting it shrink (flex:0 1 auto with min-height:0) reclaimed
   space for the queue and then drew the mode row straight through the transport keys. */
.rp-screen { flex:0 0 auto; }
.rp-plist { border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); display:flex; flex-direction:column; flex:0 0 auto; }
.rp-plist-head { position:sticky; top:0; z-index:1; }
.rp-plist-tools { display:flex; gap:4px; }
.rp-tool {
  background:none; border:1px solid var(--os-border); border-radius:5px; color:var(--os-muted);
  font:10px var(--os-mono, monospace); letter-spacing:.06em; text-transform:uppercase;
  padding:3px 7px; min-height:24px; cursor:pointer;
}
.rp-tool:hover { color:var(--os-accent); border-color:var(--os-accent); }
.rp-empty { margin:0; padding:14px 12px; font-size:12px; color:var(--os-muted); }
.rp-empty p { margin:0 0 8px; line-height:1.5; }
.rp-empty .btn { color:var(--os-bg); }
.rp-empty-or { font-size:11.5px; }
.rp-row.is-missing { opacity:.72; }
.rp-row.is-missing .rp-row-k { color:var(--os-accent); }
.rp-saved { border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); }
.rp-saved-list { list-style:none; margin:0; padding:0; }
.rp-saved-row { display:flex; align-items:center; gap:6px; padding:6px 10px; min-height:44px; }
.rp-saved-open {
  flex:1; min-width:0; display:flex; align-items:center; gap:8px; justify-content:space-between;
  background:none; border:0; color:inherit; text-align:left; cursor:pointer; font-size:13px; padding:4px 0;
}
.rp-saved-open:hover .rp-saved-name { color:var(--os-accent); }
.rp-saved-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rp-saved-n { font:10px var(--os-mono, monospace); color:var(--os-muted); flex:none; }
.rp-plist-head {
  display:flex; justify-content:space-between; gap:10px; margin:0; padding:8px 12px;
  border-bottom:1px solid var(--os-border); background:color-mix(in srgb, var(--os-panel) 70%, transparent);
  font:10px var(--os-mono, monospace); letter-spacing:.12em; text-transform:uppercase; color:var(--os-muted);
}
.rp-list { list-style:none; margin:0; padding:0; }
.rp-row {
  display:grid; grid-template-columns:34px 1fr auto auto; align-items:center; gap:10px;
  width:100%; min-height:44px; padding:0 12px; text-align:left; cursor:pointer;
  background:none; border:0; border-left:2px solid transparent; color:inherit;
}
.rp-row:hover { background:color-mix(in srgb, var(--os-accent) 7%, transparent); }
.rp-row.is-on { border-left-color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 12%, transparent); padding-left:10px; }
.rp-row-n { font:11px var(--os-mono, monospace); color:var(--os-muted); }
.rp-row.is-on .rp-row-n { color:var(--os-accent); }
.rp-row-t { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; min-width:0; }
.rp-row-k, .rp-row-d { font:10px var(--os-mono, monospace); letter-spacing:.06em; color:var(--os-muted); }
.rp-foot { margin:0; font-size:11px; color:var(--os-muted); }
.rp-info { font-size:12px; }
.rp-info summary { cursor:pointer; color:var(--os-muted); padding:4px 0; }
.rp-info summary:hover { color:var(--os-accent); }
.rp-info .note { margin:6px 0 0; }

@media (max-width: 767px), (pointer: coarse) {
  /* Small labels still need targets a thumb can hit. */
  .rp-mode { min-height:44px; }
  .rp-icon { min-height:44px; min-width:44px; }
  .rp-source { min-height:44px; }
  .rp-seek, .rp-volume { min-height:44px; }
  .rp-info summary { min-height:44px; display:flex; align-items:center; }
  .rp-tool { min-height:44px; padding:3px 10px; }
  /* The volume used to take a whole row of its own here, which cost 67px of the queue's space.
     The five keys and the slider fit across 375px together. */
  .rp-vol-wrap { flex:1 1 90px; min-width:80px; }
  .rp-time { font-size:32px; }
  .rp-vis { height:46px; }
}

/* ---- Winamp (Webamp) ---- */
/* Webamp positions its own windows absolutely, so it needs a positioned box of its own rather
   than the usual flow layout. */
.wa { display:flex; flex-direction:column; height:100%; }
.wa-status { margin:0; color:var(--os-muted); font-size:13px; }
.wa.is-ready .wa-status { display:none; }
.wa-mount { position:relative; flex:1; min-height:0; }
/* Its own chrome is the point, so it is not themed -- but it must not escape the window. */
.wa-mount #webamp { position:absolute !important; inset:0 !important; }
/* Webamp draws at Winamp's native 275px width. In a window sized for a modern screen that leaves
   it marooned in the corner, so the whole thing is scaled up. A classic skin is pixel art, so the
   image rendering is kept nearest-neighbour -- smoothing it turns crisp 1-bit edges to mush.
   Transforms scale hit areas along with the pixels, so dragging and clicking still land. */
.wa-mount { --wa-scale: 1; }
.wa-mount #webamp {
  transform: scale(var(--wa-scale));
  transform-origin: top left;
  width: calc(100% / var(--wa-scale)) !important;
  height: calc(100% / var(--wa-scale)) !important;
  image-rendering: pixelated;
}


/* A window whose app draws its own frame: no title bar, no panel, no border, no shadow. The
   desktop still owns it -- it is in the taskbar, it focuses and minimises -- but what you see is
   the app's own chrome and nothing of ours around it. */
.win.is-chromeless { background:transparent; border:0; box-shadow:none; overflow:visible; }
.win.is-chromeless > .win-body { padding:0; background:transparent; overflow:visible; }
/* The one control of ours on a chromeless window: skins load through a picker because Webamp's
   own menu cannot reach the visitor's filesystem. Kept out of the way under the skin. */
.wa-tools { margin:0; padding-top:8px; display:flex; justify-content:center; }
.wa-tools .btn { min-height:44px; font-size:13px; }

/* The screen holds three things and shows one: the analyser, a local video, or YouTube's player. */
.rp-yt { display:none; width:100%; aspect-ratio:16/9; min-height:200px; }
.rp.is-youtube .rp-vis { display:block; height:22px; }
.rp.is-youtube .rp-yt { display:block; }
.rp.is-youtube .rp-video { display:none; }
.rp-yt iframe { width:100%; height:100%; border:0; display:block; }
.rp-add { display:flex; gap:8px; margin:0; }
.rp-url { flex:1; min-width:0; min-height:44px; padding:0 10px; color:var(--os-text);
  background:var(--os-bg); border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); font:inherit; }
.rp-add .btn { min-height:44px; }

/* ---- dropping files on the desktop ---- */
/* The whole page is the target, so the cue has to be the whole page too -- a small outlined box
   would be a lie about where a drop actually works. */
body.is-dropping::after {
  content:'Drop to add — music, video, images, PDFs, JSON and code';
  position:fixed; inset:8px; z-index:2000; pointer-events:none;
  display:grid; place-items:center; text-align:center; padding:24px;
  border:2px dashed var(--os-accent); border-radius:14px;
  background:color-mix(in srgb, var(--os-bg) 72%, transparent);
  color:var(--os-accent); font:14px var(--os-mono, monospace); letter-spacing:.04em;
}

/* ---- My Files ---- */
.fl { display:flex; flex-direction:column; gap:10px; height:100%; min-height:0; overflow-y:auto; }
.fl-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0; flex-wrap:wrap; }
/* Two independent collections, each said in its own line. */
.fl-summary { list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:4px 18px; font-size:13px; color:var(--os-muted); }
.fl-summary b { color:var(--os-text); font-weight:600; }
.fl-welcome { padding:14px 16px; border:1px dashed var(--os-border); border-radius:var(--r-sm, 8px); background:color-mix(in srgb, var(--os-accent) 5%, transparent); }
.fl-welcome p { margin:0 0 8px; font-size:13px; line-height:1.5; }
.fl-welcome-actions { display:flex; flex-wrap:wrap; gap:8px; margin:0; }
.fl-added { display:flex; flex-direction:column; gap:8px; flex:0 0 auto; }
.fl-sec-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0; flex-wrap:wrap; }
.fl-note { margin:0; }
.fl-list { list-style:none; margin:0; padding:0; overflow:auto; flex:0 0 auto; max-height:34vh; display:flex; flex-direction:column; gap:2px; }
.fl-row {
  display:grid; grid-template-columns:24px minmax(0,1fr) auto auto; align-items:center; gap:10px;
  padding:6px 10px; min-height:44px; border:1px solid transparent; border-radius:var(--r-sm, 8px);
}
.fl-row:hover { background:color-mix(in srgb, var(--os-accent) 7%, transparent); }
.fl-row.is-on { border-color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 10%, transparent); }
.fl-icon { width:20px; height:20px; }
.fl-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; }
.fl-name em { font-style:normal; font-size:10px; color:var(--os-accent); text-transform:uppercase; letter-spacing:.08em; }
.fl-meta { font:10px var(--os-mono, monospace); color:var(--os-muted); white-space:nowrap; }
.fl-actions { display:flex; gap:4px; flex-wrap:wrap; justify-content:flex-end; }
.fl-empty { margin:0; padding:18px 2px; color:var(--os-muted); font-size:12px; }

/* ---- connected music folder ---- */
.fl-folder {
  border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); padding:10px 12px;
  display:flex; flex-direction:column; gap:8px; flex:0 0 auto;
}
.fl-folder-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0; flex-wrap:wrap; }
.fl-folder-state { font:10px var(--os-mono, monospace); text-transform:uppercase; letter-spacing:.1em; color:var(--os-muted); }
.fl-folder-note { margin:0; }
.fl-folder-status { margin:0; font:11px var(--os-mono, monospace); color:var(--os-accent); }
.fl-folder-list { list-style:none; margin:0; padding:0; max-height:30vh; overflow:auto; display:flex; flex-direction:column; gap:1px; }
.fl-folder-row { display:flex; align-items:center; gap:10px; min-height:40px; padding:2px 4px; border-radius:6px; }
.fl-folder-row:hover { background:color-mix(in srgb, var(--os-accent) 7%, transparent); }
.fl-folder-row.is-missing { opacity:.5; }
.fl-folder-play {
  flex:1; min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:1px;
  background:none; border:0; color:inherit; text-align:left; cursor:pointer; padding:4px 6px;
}
.fl-folder-play:disabled { cursor:default; }
.fl-folder-name { font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; }
.fl-folder-path { font:10px var(--os-mono, monospace); color:var(--os-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; }
.fl-folder-more { margin:0; font-size:11px; color:var(--os-muted); }
.fl-folder-find { display:block; margin:8px 0 4px; }
.fl-folder-q { width:100%; box-sizing:border-box; min-height:32px; padding:4px 10px; border-radius:var(--r-sm, 6px); border:1px solid var(--os-border); background:var(--os-bg); color:var(--os-text); font:inherit; font-size:13px; }
.fl-folder-q:focus-visible { outline:2px solid var(--os-accent); outline-offset:1px; }
.fl-folder-showmore { margin-top:6px; }
.fl-folder-showmore[hidden], .fl-folder-find[hidden], .fl-folder-more[hidden] { display:none; }
@media (max-width: 767px), (pointer: coarse) {
  .fl-folder-row { min-height:44px; }
  .fl-folder .rp-tool { min-height:44px; }
}
.pv { border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); padding:8px; display:flex; flex-direction:column; gap:8px; min-height:0; }
.pv-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0; font-size:12px; color:var(--os-muted); }
.pv-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; min-width:0; }
.pv-zoom { display:flex; align-items:center; gap:4px; }
.pv-zoom-level { font:10px var(--os-mono, monospace); color:var(--os-muted); width:40px; text-align:center; }
/* The stage scrolls; the picture inside it grows. Scaling without a scroller means the enlarged
   half of the image is simply unreachable. */
.pv-stage { overflow:auto; flex:1 1 auto; min-height:0; display:grid; place-items:center; background:color-mix(in srgb, var(--os-bg) 60%, #000); border-radius:4px; }
/* No max-width: the size is set in pixels by applyZoom, and a CSS cap here was the second of the
   two scalings that made "100%" mean 56%. */
.pv-stage img { transform-origin:center center; flex:none; }
.pv-stage iframe { width:100%; height:100%; border:1px solid var(--os-border); border-radius:4px; background:#fff; }

@media (max-width: 767px), (pointer: coarse) {
  .pv .rp-tool { min-width:44px; min-height:44px; }
}

/* ---- the editor ---- */
.ed {
  --ed-font:13px; --ed-split:50%;
  display:flex; flex-direction:column; gap:8px; height:100%; min-height:0; position:relative;
}

/* Tabs: what is open, which one is showing, and what is unsaved -- one row answers all three. */
.ed-tabs { display:flex; gap:2px; overflow-x:auto; overflow-y:hidden; scrollbar-width:thin; flex:0 0 auto; }
/* A tab gives up width down to a readable minimum before the strip starts scrolling, so four or
   five files are all visible at once instead of two and a scrollbar. Below that it scrolls rather
   than shrinking to a sliver nobody can read. */
.ed-tab {
  display:flex; align-items:center; flex:0 1 auto; min-width:104px; max-width:200px;
  border:1px solid var(--os-border); border-bottom-color:transparent; border-radius:6px 6px 0 0;
  background:color-mix(in srgb, var(--os-panel) 55%, transparent);
}
.ed-tab.is-on { background:color-mix(in srgb, var(--os-accent) 12%, transparent); border-color:var(--os-accent); }
.ed-tab-name {
  display:flex; align-items:center; gap:6px; min-width:0; flex:1 1 auto; padding:6px 4px 6px 10px;
  background:none; border:0; color:inherit; font:12px var(--os-mono, monospace); cursor:pointer;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.ed-tab.is-on .ed-tab-name { color:var(--os-accent); }
.ed-dot { width:6px; height:6px; border-radius:50%; background:var(--os-accent); flex:none; }
.ed-tab-x {
  background:none; border:0; color:var(--os-muted); cursor:pointer; padding:4px 8px; font-size:14px; line-height:1;
}
.ed-tab-x:hover { color:var(--os-accent); }

.ed-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0; flex-wrap:wrap; }
.ed-actions { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.ed-json { display:flex; gap:4px; }
.ed-gear .i, .ed-icon .i { width:15px; height:15px; background:currentColor; mask:var(--i) center/contain no-repeat; -webkit-mask:var(--i) center/contain no-repeat; display:block; }

.ed-settings {
  position:absolute; right:0; top:64px; z-index:20; width:min(240px, 90%);
  display:flex; flex-direction:column; gap:8px; padding:12px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--r-md, 10px);
  box-shadow:0 12px 32px rgba(0,0,0,.5);
}
.ed-set { display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:12px; }
.ed-set-check { justify-content:flex-start; }
.ed-set select { background:var(--os-bg); color:var(--os-text); border:1px solid var(--os-border); border-radius:6px; padding:4px 6px; font:inherit; }
.ed-set input[type=checkbox] { accent-color:var(--os-accent); width:16px; height:16px; }

/* The toolbar. Grouped, so eleven pills read as three sets, and the marks are drawn rather than
   typed -- a bullet character and a curly quote are the text-glyph-as-icon this project bans. */
.ed-tools { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.ed-group { display:flex; gap:2px; padding:2px; border:1px solid var(--os-border); border-radius:8px; }
.ed-tools .rp-tool { min-width:32px; border:0; font-family:inherit; font-size:12px; text-transform:none; letter-spacing:0; }
.ed-tools .rp-tool:hover { background:color-mix(in srgb, var(--os-accent) 14%, transparent); }
.ed-b { font-weight:800; }
.ed-i { font-style:italic; font-family:Georgia, serif; }
.ed-s { text-decoration:line-through; }
/* Preview was a 58x24 muted pill at the far right, and read as unrelated to the toolbar it sits in
   -- easy to miss entirely. It is a bordered group like the others, carries a mark, and its on-state
   is the accent rather than a shade of grey. */
.ed-preview-group { margin-left:auto; }
.ed-preview-btn { display:inline-flex; align-items:center; gap:6px; padding-inline:10px; }
.ed-preview-btn .i { width:15px; height:15px; background:currentColor; mask:var(--i) center/contain no-repeat; -webkit-mask:var(--i) center/contain no-repeat; }
.ed-preview-btn[aria-pressed="true"] { color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 16%, transparent); }
.ed-preview-group:has([aria-pressed="true"]) { border-color:var(--os-accent); }
.ed-more { display:none; }
.ed.is-narrow .ed-more { display:grid; place-items:center; }
.ed.is-narrow .ed-more-group { display:none; }
.ed.is-narrow.is-more .ed-more-group { display:flex; flex-basis:100%; }

.ed-body {
  flex:1 1 auto; min-height:0; display:grid; grid-template-columns:auto 1fr;
  border:1px solid var(--os-border); border-radius:var(--r-sm, 8px); overflow:hidden;
  background:color-mix(in srgb, var(--os-bg) 70%, #000);
}
.ed-gutter {
  overflow:hidden; padding:10px 8px; text-align:right; user-select:none;
  font:var(--ed-font)/1.5 var(--os-mono, monospace); color:var(--os-muted);
  background:color-mix(in srgb, var(--os-panel) 60%, transparent); border-right:1px solid var(--os-border);
}
.ed-gutter span { display:block; }
.ed-area {
  border:0; resize:none; padding:10px 12px; min-width:0; color:var(--os-text); background:transparent;
  font:var(--ed-font)/1.5 var(--os-mono, monospace); tab-size:2; white-space:pre; overflow:auto;
}
.ed-area:focus { outline:none; }

/* The preview is a panel with a name on it, and the bar between them can be dragged. */
.ed.is-previewing .ed-body { grid-template-columns:auto var(--ed-split) 6px 1fr; }
.ed-split { cursor:col-resize; background:var(--os-border); }
.ed-split:hover, .ed-split:focus-visible { background:var(--os-accent); outline:none; }
.ed-preview { display:flex; flex-direction:column; min-width:0; background:color-mix(in srgb, var(--os-panel) 40%, transparent); }
.ed-preview-head {
  margin:0; padding:6px 12px; border-bottom:1px solid var(--os-border);
  font:10px var(--os-mono, monospace); letter-spacing:.12em; text-transform:uppercase; color:var(--os-muted);
}
.ed-preview-body { overflow:auto; padding:10px 14px; font-size:14px; line-height:1.6; min-height:0; }
.ed-preview-body h1, .ed-preview-body h2, .ed-preview-body h3 { margin:.6em 0 .3em; line-height:1.25; }
.ed-preview-body h1 { font-size:1.5em; } .ed-preview-body h2 { font-size:1.25em; } .ed-preview-body h3 { font-size:1.1em; }
.ed-preview-body p, .ed-preview-body ul, .ed-preview-body ol, .ed-preview-body blockquote, .ed-preview-body pre { margin:.5em 0; }
.ed-preview-body ul, .ed-preview-body ol { padding-left:1.4em; }
.ed-preview-body a { color:var(--os-accent); }
.ed-preview-body code { font:0.92em var(--os-mono, monospace); background:color-mix(in srgb, var(--os-accent) 12%, transparent); padding:1px 4px; border-radius:3px; }
.ed-preview-body pre { background:color-mix(in srgb, var(--os-bg) 70%, #000); padding:10px; border-radius:6px; overflow:auto; }
.ed-preview-body pre code { background:none; padding:0; }
.ed-preview-body blockquote { border-left:2px solid var(--os-accent); padding-left:10px; color:var(--os-muted); }
.ed-preview-body hr { border:0; border-top:1px solid var(--os-border); }

/* The empty state used to be a disabled grey box, which says nothing about what this opens. */
.ed-blank {
  position:absolute; inset:auto 0 0; top:0; z-index:5;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  padding:24px; text-align:center; background:var(--os-bg);
}
.ed-blank-title { margin:0; font:15px var(--os-mono, monospace); color:var(--os-text); }
.ed-blank-note { margin:0; max-width:46ch; font-size:13px; color:var(--os-muted); line-height:1.6; }
.ed-blank-actions { display:flex; gap:8px; flex-wrap:wrap; justify-content:center; margin:6px 0 0; }
.ed.is-empty .ed-body, .ed.is-empty .ed-status, .ed.is-empty .ed-tools { visibility:hidden; }

/* Asked inside the window it belongs to, so the three answers can be three buttons rather than the
   two a browser confirm allows. */
.ed-unsaved {
  position:absolute; inset:0; z-index:30; display:grid; place-items:center; padding:20px;
  background:color-mix(in srgb, var(--os-bg) 72%, transparent);
}
.ed-unsaved-card {
  width:min(420px, 100%); padding:18px; display:flex; flex-direction:column; gap:10px;
  background:var(--os-panel); border:1px solid var(--os-accent); border-radius:var(--r-md, 10px);
  box-shadow:0 16px 40px rgba(0,0,0,.55);
}
.ed-unsaved-title { margin:0; font-size:15px; }
.ed-unsaved-body { margin:0; font-size:13px; color:var(--os-muted); line-height:1.6; }
.ed-unsaved-actions { display:flex; gap:8px; flex-wrap:wrap; margin:4px 0 0; }

.ed-status {
  display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0;
  font:11px var(--os-mono, monospace); color:var(--os-muted);
}
.ed-status-left, .ed-status-right { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

@media (max-width: 767px), (pointer: coarse) {
  /* Height alone is not a touch target: these were 30px wide and 44 tall. */
  .ed-tools .rp-tool, .ed-head .rp-tool { min-width:44px; min-height:44px; }
  .ed-tab-name { min-height:44px; }
  .ed-tab-x { min-width:44px; min-height:44px; }
  /* Side by side at 375px would be two unreadable columns. */
  .ed.is-previewing .ed-body { grid-template-columns:auto 1fr; grid-template-rows:1fr 6px 1fr; }
  .ed-split { cursor:row-resize; grid-column:1 / -1; }
  .ed-preview { grid-column:1 / -1; border-top:1px solid var(--os-border); }
}

@media (max-width: 767px), (pointer: coarse) {
  .fl-row { grid-template-columns:24px minmax(0,1fr); grid-auto-rows:auto; }
  .fl-meta, .fl-actions { grid-column:1 / -1; justify-content:flex-start; }
}

/* ---- tray volume applet ---- */
.volume-panel {
  position:fixed; right:8px; bottom:calc(var(--os-taskbar) + 8px); z-index:1100; width:min(300px, calc(100vw - 16px));
  padding:12px; display:flex; flex-direction:column; gap:8px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--r-md, 10px);
  box-shadow:0 12px 32px rgba(0,0,0,.5);
}
.vol-head { margin:0; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.vol-row { display:flex; align-items:center; gap:10px; }
.vol-slider { flex:1; min-width:0; accent-color:var(--os-accent); }
.vol-pct { font:11px var(--os-mono, monospace); color:var(--os-muted); width:36px; text-align:right; }
.vol-note { margin:0; font-size:11px; }
@media (max-width: 767px), (pointer: coarse) { .vol-slider { min-height:44px; } }

/* ---- the editor's formatting toolbar ---- */
.ed-tools { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.ed-tools .rp-tool { min-width:30px; font-family:inherit; font-size:12px; text-transform:none; letter-spacing:0; }
.ed-sep { width:1px; align-self:stretch; background:var(--os-border); margin:0 4px; }
.ed.is-previewing .ed-body { grid-template-columns:auto 1fr 1fr; }
.ed-preview {
  overflow:auto; padding:10px 14px; border-left:1px solid var(--os-border); min-width:0;
  background:color-mix(in srgb, var(--os-panel) 40%, transparent); font-size:14px; line-height:1.6;
}
.ed-preview h1, .ed-preview h2, .ed-preview h3 { margin:.6em 0 .3em; line-height:1.25; }
.ed-preview h1 { font-size:1.5em; } .ed-preview h2 { font-size:1.25em; } .ed-preview h3 { font-size:1.1em; }
.ed-preview p, .ed-preview ul, .ed-preview ol, .ed-preview blockquote, .ed-preview pre { margin:.5em 0; }
.ed-preview ul, .ed-preview ol { padding-left:1.4em; }
.ed-preview a { color:var(--os-accent); }
.ed-preview code { font:0.92em var(--os-mono, monospace); background:color-mix(in srgb, var(--os-accent) 12%, transparent); padding:1px 4px; border-radius:3px; }
.ed-preview pre { background:color-mix(in srgb, var(--os-bg) 70%, #000); padding:10px; border-radius:6px; overflow:auto; }
.ed-preview pre code { background:none; padding:0; }
.ed-preview blockquote { border-left:2px solid var(--os-accent); padding-left:10px; color:var(--os-muted); }
.ed-preview hr { border:0; border-top:1px solid var(--os-border); }
@media (max-width: 767px), (pointer: coarse) {
  .ed.is-previewing .ed-body { grid-template-columns:auto 1fr; grid-auto-rows:1fr 1fr; }
  .ed-preview { border-left:0; border-top:1px solid var(--os-border); grid-column:1 / -1; }
}

/* ---- CodeMirror, wearing the OS's own colours ---- */
/* No theme file is vendored: the editor reads the same --os-* tokens as everything else, so it
   follows all four themes instead of carrying a fifth.
   The token rules are `.ed .CodeMirror .cm-x` and not `.ed .cm-x` on purpose. CodeMirror's own
   sheet styles them as `.cm-s-default .cm-x`, which is the SAME specificity and loads after this
   file -- so at `.ed .cm-x` every colour here silently lost and a Markdown heading rendered in
   CodeMirror's stock blue. Measured, not guessed: rgb(0, 0, 255) against an accent of #E9B383. */
.ed .CodeMirror {
  height:100%; background:transparent; color:var(--os-text);
  /* var, not 13px: the Text size setting is on .ed and this rule was quietly ignoring it. */
  font:var(--ed-font, 13px)/1.5 var(--os-mono, monospace);
}
.ed .CodeMirror-gutters { background:color-mix(in srgb, var(--os-panel) 60%, transparent); border-right:1px solid var(--os-border); }
.ed .CodeMirror-linenumber { color:var(--os-muted); }
.ed .CodeMirror-cursor { border-left:2px solid var(--os-accent); }
.ed .CodeMirror-selected { background:color-mix(in srgb, var(--os-accent) 24%, transparent); }
.ed .CodeMirror-focused .CodeMirror-selected { background:color-mix(in srgb, var(--os-accent) 30%, transparent); }
.ed .CodeMirror-activeline-background { background:color-mix(in srgb, var(--os-accent) 6%, transparent); }
.ed .CodeMirror .CodeMirror-matchingbracket { color:var(--os-accent); border-bottom:1px solid var(--os-accent); }
.ed .CodeMirror .CodeMirror-nonmatchingbracket { color:#ff8b7a; }

.ed .CodeMirror .cm-comment { color:var(--os-muted); font-style:italic; }
.ed .CodeMirror .cm-string,
.ed .CodeMirror .cm-string-2 { color:color-mix(in srgb, var(--os-accent) 82%, var(--os-text)); }
.ed .CodeMirror .cm-number,
.ed .CodeMirror .cm-atom { color:color-mix(in srgb, var(--os-accent) 70%, #fff); }
.ed .CodeMirror .cm-keyword,
.ed .CodeMirror .cm-tag { color:var(--os-accent); font-weight:600; }
.ed .CodeMirror .cm-def,
.ed .CodeMirror .cm-variable-2,
.ed .CodeMirror .cm-variable-3,
.ed .CodeMirror .cm-attribute { color:color-mix(in srgb, var(--os-text) 78%, var(--os-accent)); }
.ed .CodeMirror .cm-variable { color:var(--os-text); }
.ed .CodeMirror .cm-property,
.ed .CodeMirror .cm-qualifier { color:color-mix(in srgb, var(--os-text) 62%, var(--os-accent)); }
.ed .CodeMirror .cm-operator,
.ed .CodeMirror .cm-punctuation,
.ed .CodeMirror .cm-meta { color:var(--os-muted); }
.ed .CodeMirror .cm-builtin { color:color-mix(in srgb, var(--os-accent) 60%, var(--os-text)); }
/* Markdown */
.ed .CodeMirror .cm-header { color:var(--os-accent); font-weight:700; }
.ed .CodeMirror .cm-quote { color:var(--os-muted); font-style:italic; }
.ed .CodeMirror .cm-link { color:var(--os-accent); }
.ed .CodeMirror .cm-url { color:var(--os-muted); }
.ed .CodeMirror .cm-strong { color:var(--os-text); font-weight:700; }
.ed .CodeMirror .cm-em { color:var(--os-text); font-style:italic; }
.ed .CodeMirror .cm-hr { color:var(--os-border); }
.ed .CodeMirror .cm-error { color:#ff8b7a; }

/* The find bar is CodeMirror's own dialog; it only needs to stop looking like a 2011 web page. */
.ed .CodeMirror-dialog {
  background:var(--os-panel); border-bottom:1px solid var(--os-border); color:var(--os-text);
  padding:6px 10px; font:12px var(--os-mono, monospace);
}
.ed .CodeMirror-dialog input { background:var(--os-bg); border:1px solid var(--os-border); color:var(--os-text); border-radius:4px; padding:3px 6px; font:inherit; }
/* When CodeMirror is mounted the hand-rolled gutter is hidden, so the body is one column. */
/* With CodeMirror mounted the hand-rolled gutter is gone, so the body loses its first column --
   but it must keep the SAME split template, or the draggable separator has nothing to move. */
.ed .ed-body:has(.CodeMirror) { grid-template-columns:1fr; grid-template-rows:1fr; }
.ed.is-previewing .ed-body:has(.CodeMirror) { grid-template-columns:var(--ed-split) 6px 1fr; }
@media (max-width: 767px), (pointer: coarse) {
  .ed.is-previewing .ed-body:has(.CodeMirror) { grid-template-columns:1fr; grid-template-rows:1fr 6px 1fr; }
}

@media (max-width: 767px), (pointer: coarse) {
  /* Height alone is not a touch target: these were 30px wide and 44 tall. */
  .ed-tools .rp-tool { min-width:44px; }
}

.pv-gone { margin:0; padding:24px; text-align:center; font-size:13px; line-height:1.7; color:var(--os-muted); }

/* ---- Calculator ---- */
/* The window is 320px wide and the keypad has to fit INSIDE it: the first version was 497px tall
   in a 418px body with no scroll, so the bottom row -- including the equals key -- was simply
   unreachable. The screen takes what it needs and the keypad takes the rest. */
.app-calculator { height:100%; min-height:0; display:flex; }
.app-calculator > .calc { flex:1 1 auto; min-width:0; }
/* overflow-y:auto, not hidden: six rows at the 44px touch minimum need 294px, and a window short
   enough to squeeze them was clipping the equals key off the bottom with no way to reach it.
   Scrolling is the honest last resort; the row sizing below means it almost never happens. */
.calc { display:flex; flex-direction:column; height:100%; min-height:0; background:var(--os-bg); color:var(--os-text); overflow-y:auto; overflow-x:hidden; }
/* The screen has a FIXED height. It grew with the history before, so a third sum started eating
   the keypad -- on a real calculator the keys never move. The history scrolls inside its own strip
   instead, showing the last few lines with the rest a scroll away. */
.calc-screen {
  flex:0 0 auto; height:116px; display:flex; flex-direction:column; justify-content:flex-end;
  gap:4px; padding:10px 12px; overflow:hidden;
}
.calc-history {
  flex:0 1 auto; min-height:0; max-height:46px; overflow-y:auto; scrollbar-width:thin;
  display:flex; flex-direction:column; align-items:flex-end; gap:2px;
}
.calc-hist-item {
  background:none; border:0; color:var(--os-muted); font:11px var(--os-mono, monospace);
  padding:2px 4px; cursor:pointer; border-radius:var(--r-sm, 4px);
}
.calc-hist-item:hover, .calc-hist-item:focus-visible { color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 12%, transparent); }
.calc-expr { margin:0; text-align:right; font:12px var(--os-mono, monospace); color:var(--os-muted); min-height:1.2em; word-break:break-all; }
.calc-res {
  margin:0; text-align:right; font:30px/1.1 var(--os-mono, monospace); color:var(--os-accent);
  font-variant-numeric:tabular-nums;
  background:transparent; border:none; outline:none; padding:0; width:100%;
}
/* The keypad gets whatever height is left, and its rows share it equally. */
.calc-keypad {
  flex:1 1 auto; min-height:0; display:grid; gap:6px; padding:0 10px 10px;
  /* minmax(0,1fr) so the rows always divide the space they have rather than insisting on a
     height the window cannot give them. */
  grid-template-columns:repeat(4, 1fr); grid-auto-rows:minmax(0, 1fr); min-height:0;
}
.calc-btn {
  display:grid; place-items:center; min-width:0; cursor:pointer;
  border:1px solid var(--os-border); border-radius:var(--r-md, 8px); font:15px var(--os-mono, monospace);
  box-shadow:0 2px 0 var(--os-border); transition:transform 0.05s, box-shadow 0.05s;
}
.calc-btn:hover, .calc-btn:focus-visible { border-color:var(--os-accent); color:var(--os-accent); }
.calc-btn:active { transform:translateY(2px); box-shadow:0 0 0 var(--os-border); }
.calc-digit { background:var(--os-panel); color:var(--os-text); font-weight:600; }
.calc-op { background:color-mix(in srgb, var(--os-bg) 60%, var(--os-panel)); color:var(--os-muted); }
.calc-span-2 { grid-column:span 2; }
.calc-accent { background:var(--os-accent); border-color:var(--os-accent); color:var(--os-bg); box-shadow:0 2px 0 color-mix(in srgb, var(--os-accent) 60%, var(--os-text)); }
.calc-accent:hover, .calc-accent:focus-visible { color:var(--os-bg); filter:brightness(1.08); }
.calc-accent:active { transform:translateY(2px); box-shadow:0 0 0 color-mix(in srgb, var(--os-accent) 60%, var(--os-text)); }
@media (max-width: 767px), (pointer: coarse) {
  .calc-btn { min-height:44px; min-width:44px; }
  .calc-keypad { grid-auto-rows:minmax(44px, 1fr); }   /* touch keeps its floor; .calc scrolls if it must */
}

/* Preview's empty state and its Markdown reader. The document reuses the editor's preview styling
   so one renderer produces one look in both places. */
.pv-empty { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; height:100%; padding:24px; text-align:center; }
.pv-empty-title { margin:0; font:15px var(--os-mono, monospace); }
.pv-empty-note { margin:0; max-width:44ch; font-size:13px; line-height:1.6; color:var(--os-muted); }
.pv-stage.is-doc { display:block; overflow:auto; background:transparent; }
.pv-doc { padding:16px 20px; font-size:14px; line-height:1.7; max-width:72ch; margin:0 auto; }
.pv-doc h1, .pv-doc h2, .pv-doc h3 { margin:.7em 0 .3em; line-height:1.25; }
.pv-doc h1 { font-size:1.6em; } .pv-doc h2 { font-size:1.3em; } .pv-doc h3 { font-size:1.1em; }
.pv-doc p, .pv-doc ul, .pv-doc ol, .pv-doc blockquote, .pv-doc pre { margin:.6em 0; }
.pv-doc ul, .pv-doc ol { padding-left:1.4em; }
.pv-doc a { color:var(--os-accent); }
.pv-doc code { font:0.92em var(--os-mono, monospace); background:color-mix(in srgb, var(--os-accent) 12%, transparent); padding:1px 4px; border-radius:3px; }
.pv-doc pre { background:color-mix(in srgb, var(--os-bg) 70%, #000); padding:12px; border-radius:6px; overflow:auto; }
.pv-doc pre code { background:none; padding:0; }
.pv-doc blockquote { border-left:2px solid var(--os-accent); padding-left:12px; color:var(--os-muted); }
.pv-doc hr { border:0; border-top:1px solid var(--os-border); }

/* ---- tooltips ---- */
/* pointer-events:none so the tip can never sit between the pointer and the control it describes and
   cause its own pointerout. No animation under reduced motion. */
.ros-tip {
  position:fixed; z-index:3000; max-width:260px; padding:6px 10px; pointer-events:none;
  background:var(--os-panel); color:var(--os-text); border:1px solid var(--os-border); border-radius:var(--r-sm, 8px);
  font:12px/1.4 system-ui, sans-serif; box-shadow:0 8px 24px rgba(0,0,0,.45);
}
.ros-tip[hidden] { display:none; }
@media (prefers-reduced-motion: no-preference) {
  .ros-tip { animation:ros-tip-in .12s ease-out; }
  @keyframes ros-tip-in { from { opacity:0; transform:translateY(2px); } }
}

/* ---- Winamp looks ---- */
.wa-tools { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0; padding:8px 4px 0; }
.wa-look { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--os-muted); }
.wa-look select { background:var(--os-bg); color:var(--os-text); border:1px solid var(--os-border); border-radius:6px; padding:5px 8px; font:inherit; min-height:32px; }
@media (max-width: 767px), (pointer: coarse) { .wa-look select, .wa-tools .btn { min-height:44px; } }

/* ---- Winamp: visualiser, skins ---- */
/* The recolour is applied to the three classic windows only. Put on the whole mount it also
   hue-shifted (or greyed) the Milkdrop window, which is a different picture, not a different look. */
.wa-mount #webamp #main-window,
.wa-mount #webamp #equalizer-window,
.wa-mount #webamp #playlist-window { filter: var(--wa-filter, none); }
.wa-look[hidden] { display:none; }
.wa-look select:disabled { opacity:.45; }
.wa-viz { display:inline-flex; gap:6px; flex-wrap:wrap; }
.wa-viz[hidden] { display:none; }
.wa-tools .btn[aria-pressed="true"] { border-color:var(--os-accent); box-shadow:0 0 0 1px var(--os-accent); }
.wa-tools .btn:disabled { opacity:.45; cursor:default; }

/* Desktop Mode. Webamp drops a `.webamp-desktop` div on <body> with z-index -1, which puts it
   beneath the desktop's own opaque wallpaper, so it was there and never seen. Raised to sit ON the
   wallpaper and UNDER the desktop layer (icons and windows), and given the whole screen above the
   taskbar. pointer-events off: it is a picture, and must never eat a click meant for an icon. */
body.wa-viz-desktop .webamp-desktop { position:fixed; inset:0 0 var(--os-dock) 0; z-index:0; pointer-events:none; }
body.wa-viz-desktop .desktop { z-index:1; }

/* The visualiser's colours: a monochrome tint from the live accent, the same recipe as the player
   look, on the Milkdrop canvas only -- in its window, on the desktop, and full-screen. */
.wa-mount #webamp .window:not(#main-window):not(#equalizer-window):not(#playlist-window) canvas,
.webamp-desktop canvas { filter: var(--wa-viz-filter, none); }
.wa-viz select { max-width:190px; }

/* The Credits link in the desktop's small print. The stamp itself never takes a click, so it cannot
   eat one meant for the wallpaper; only the link does. */
#build-stamp .stamp-link { pointer-events:auto; background:none; border:0; padding:4px 0 4px 2px; margin:0; font:inherit; color:var(--os-accent); text-decoration:underline; text-underline-offset:2px; cursor:pointer; }
#build-stamp .stamp-link:hover { opacity:1; }
/* A footer link on a phone still has to be a thumb-sized target. */
.m-foot a[data-open] { display:inline-block; min-width:44px; min-height:44px; line-height:44px; padding:0 12px; color:var(--os-accent); }

/* The Winamp controls live in a popover. They used to sit in a strip under the player, which grew
   to 250px once the visualiser's controls joined it -- on a normal-height screen that overlapped the
   bottom of the playlist, and the player looked lost behind its own settings. Closed, this is one
   44px button; open, it floats above the player and takes nothing from it. */
.wa-tools { position:relative; z-index:20; display:flex; justify-content:center; padding:8px 4px 0; }
.wa-custom-btn { min-height:44px; }
.wa-custom-btn[aria-expanded="true"] { border-color:var(--os-accent); box-shadow:0 0 0 1px var(--os-accent); }
.wa-panel { position:absolute; left:8px; right:8px; bottom:calc(100% - 4px); display:flex; flex-direction:column; gap:10px; padding:12px;
  background:var(--os-panel); border:1px solid var(--os-border); border-radius:var(--r-md, 10px); box-shadow:0 12px 40px rgba(0,0,0,.55); }
.wa-panel[hidden] { display:none; }
.wa-row { display:flex; flex-wrap:wrap; align-items:center; gap:8px 10px; }
.wa-viz { display:flex; }

/* Winamp on the phone. There is no fixed-height window around it there, so the flex:1 player area
   collapsed to 0px: the three Webamp windows spilled out of it and the Customise button sat on top
   of the player's title bar. The stack is 275 x 348 in Webamp's own pixels, so the area is given
   exactly that, scaled, and the controls sit below it in the normal flow. */
.m-body .wa { height:auto; display:block; }
.m-body .wa-mount { height:calc(348px * var(--wa-scale)); }
.m-body .wa-tools { margin-top:4px; }

.wa-help { margin:0; font-size:13px; line-height:1.5; color:var(--os-muted); }
.wa-help strong { color:var(--os-text); }
.wa-warn { margin:0; padding:8px 10px; font-size:12px; line-height:1.45; color:var(--os-text);
  background:color-mix(in srgb, #e0a030 12%, transparent); border:1px solid color-mix(in srgb, #e0a030 55%, transparent); border-radius:8px; }
.wa-warn strong { color:#e6b04a; }

/* The player is drawn at whole-number sizes (see winamp.js): 1x, or Webamp's own Double Size for 2x.
   --wa-stack / --wa-stack-w are the classic stack's size in Webamp's pixels at each. */
.wa-mount { --wa-stack:348px; --wa-stack-w:275px; }
.wa-mount.is-doubled { --wa-stack:696px; --wa-stack-w:550px; }
.m-body .wa-mount { height:var(--wa-stack); width:var(--wa-stack-w); max-width:100%; margin:0 auto; }
.m-body .wa-tools { justify-content:center; }
.wa-panel { left:0; right:auto; width:min(520px, calc(100vw - 24px)); }

/* The Customise panel scrolls inside itself when the screen is short, and is placed by winamp.js. */
.wa-panel { overflow-y:auto; overscroll-behavior:contain; }

.music-resume { margin-top:10px; max-width:100%; white-space:normal; text-align:left; }

/* RW Player queue editing */
.rp-li { display:flex; align-items:stretch; gap:4px; }
.rp-li > .rp-row { flex:1; min-width:0; }
.rp-li.is-dragging { opacity:.45; }
.rp-row-tools { display:flex; align-items:center; gap:2px; }
.rp-mini { width:36px; min-width:36px; height:44px; padding:0; display:grid; place-items:center; background:none; border:1px solid transparent; border-radius:6px; color:var(--os-muted); }
.rp-mini:hover:not(:disabled), .rp-mini:focus-visible { border-color:var(--os-border); color:var(--os-text); }
.rp-mini:disabled { opacity:.3; }
.rp-mini .i { width:16px; height:16px; }
.rp-mini-down .i { transform:rotate(180deg); }
.rp-saved-row.is-default .rp-saved-n { color:var(--os-accent); }
.rp-saved-row[draggable="true"] { cursor:grab; }
@media (pointer: fine) { .rp-row-tools { opacity:.0; } .rp-li:hover .rp-row-tools, .rp-li:focus-within .rp-row-tools { opacity:1; } }

/* A certificate in Preview: the picture fills the window, its details sit underneath. */
.pv-foot { padding:10px 14px 12px; border-top:1px solid var(--os-border); display:grid; gap:6px; }
.pv-foot p { margin:0; }
.pv-cert-line { font-size:14px; }
.pv-cert-about { font-size:13px; color:var(--os-muted); line-height:1.45; }
.pv-cert-verify { display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px; }

/* The Customise panel has its own close button: people did not realise the Customise button was also
   the way to dismiss it. Kept at the top while the panel scrolls. */
.wa-panel-head { position:sticky; top:-12px; z-index:1; display:flex; align-items:center; justify-content:space-between; gap:8px;
  margin:-12px -12px 0; padding:8px 8px 8px 14px; background:var(--os-panel); border-bottom:1px solid var(--os-border); }
.wa-close { width:44px; height:44px; display:grid; place-items:center; background:none; border:1px solid transparent; border-radius:8px; color:var(--os-text); cursor:pointer; }
.wa-close:hover, .wa-close:focus-visible { border-color:var(--os-border); background:color-mix(in srgb, var(--os-accent) 9%, transparent); }
.wa-close .i { width:18px; height:18px; }

/* Icon buttons. A mark instead of a word, with the word kept as the accessible name and the tooltip. */
.ico-btn { min-width:44px; min-height:44px; padding:0 !important; display:inline-grid; place-items:center; }
.ico-btn .glyph { width:20px; height:20px; }
.rp-plist-tools, .fl-actions { display:inline-flex; flex-wrap:wrap; gap:4px; align-items:center; }
.rp-source { display:inline-flex; align-items:center; justify-content:center; gap:7px; }
.rp-source .glyph { width:18px; height:18px; }
.wa-custom-btn { display:inline-flex; align-items:center; gap:8px; }
.wa-custom-btn .glyph { width:18px; height:18px; }

/* Winamp panel tiles: the mark above a short caption, in a grid, instead of a wall of wide text buttons. */
.wa-group-title { margin:2px 0 -4px; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--os-muted); }
.wa-tiles { display:grid; grid-template-columns:repeat(auto-fill, minmax(88px, 1fr)); gap:8px; }
.wa-tile { min-height:64px; padding:8px 6px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px;
  background:color-mix(in srgb, var(--os-bg) 70%, transparent); color:var(--os-text); border:1px solid var(--os-border); border-radius:10px; cursor:pointer; text-align:center; font:inherit; }
.wa-tile .glyph { width:22px; height:22px; }
.wa-tile-label { font-size:12px; line-height:1.2; }
.wa-tile:hover:not(:disabled), .wa-tile:focus-visible { border-color:var(--os-accent); }
.wa-tile:disabled { opacity:.4; cursor:default; }
.wa-tile[aria-pressed="true"] { border-color:var(--os-accent); box-shadow:0 0 0 1px var(--os-accent); background:color-mix(in srgb, var(--os-accent) 12%, transparent); }
.wa-tile-primary { border-color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 16%, transparent); }
.wa-viz { display:grid; gap:10px; }
.wa-viz[hidden] { display:none; }

/* RW Player: Manage mode */
.rp-manage-btn { display:inline-flex; align-items:center; gap:6px; min-height:44px; }
.rp-manage-btn .glyph { width:16px; height:16px; }
.rp-manage-btn[aria-pressed="true"] { border-color:var(--os-accent); color:var(--os-accent); }
.rp-manage { display:grid; gap:8px; padding:10px; margin-bottom:8px; background:color-mix(in srgb, var(--os-bg) 70%, transparent); border:1px solid var(--os-border); border-radius:8px; }
.rp-manage[hidden] { display:none; }
.rp-manage-count { margin:0; font-size:13px; font-weight:600; }
.rp-manage-actions, .rp-manage-groups { display:flex; flex-wrap:wrap; gap:6px; }
.rp-manage-actions .rp-tool { min-height:40px; display:inline-flex; align-items:center; gap:6px; }
.rp-manage-actions .glyph { width:16px; height:16px; }
.rp-danger:not(:disabled) { border-color:color-mix(in srgb, #e0685a 70%, transparent); color:#f0a196; }
.rp-danger:disabled { opacity:.4; }
.rp-manage-hint { margin:2px 0 0; font-size:12px; color:var(--os-muted); }
.rp-chip { min-height:36px; padding:0 12px; background:none; color:inherit; border:1px solid var(--os-border); border-radius:999px; cursor:pointer; font-size:12px; }
.rp-chip b { color:var(--os-accent); margin-left:4px; }
.rp-chip[aria-pressed="true"] { border-color:var(--os-accent); background:color-mix(in srgb, var(--os-accent) 14%, transparent); }
.rp-pick { flex:1; min-width:0; min-height:44px; display:flex; align-items:center; gap:10px; padding:0 8px; cursor:pointer; border-radius:6px; }
.rp-pick:hover { background:color-mix(in srgb, var(--os-accent) 8%, transparent); }
.rp-pick input { width:20px; height:20px; accent-color:var(--os-accent); flex:none; }
.rp-pick .rp-row-t { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.rp-mg-down .glyph { transform:rotate(180deg); }
