/* ==========================================================================
   desktop-os-theme / shell
   --------------------------------------------------------------------------
   The operating system: desktop scene, icons, windows, taskbar, launcher.
   This file is the shell class's equivalent of a register theme's play
   layer, except it is not a garnish; it is the archetype. Delete it and
   shell.js and you still have a readable page built from tokens.css and
   components.css, which is the JS-last contract this class is built on.

   HOW THE PROGRESSIVE ENHANCEMENT WORKS. The markup is an ordinary document:
   a header, a nav of anchor links, window articles in reading order, a
   footer nav. This file styles that document twice:

     1. The base styles below assume no JavaScript. Windows are stacked
        cards in document order, the icon nav is a visible grid of anchor
        links, and the taskbar's shortcut strip is a nav that jumps to each
        window. The page scrolls like a page. Everything is reachable and
        readable.

     2. shell.js puts `os-live` on the <html> element as its first act.
        Every rule scoped under .os-live upgrades the same markup into an
        operating system: the page stops scrolling, windows become openable
        surfaces that float above the desktop, icons open them, the taskbar
        tracks them beside the shortcut strip that does not change. No
        markup is duplicated; the document is the OS.

   There is not one color literal in this file. Every color, every duration
   and every piece of shell geometry is a var() reference to tokens.css.

   What is a bare length here, and why, because the broader claim this
   header used to make was false. Four kinds, and nothing else:

     1. Control and target minimums that exist to satisfy a standard rather
        than a design: min-width/min-height 44px (WCAG 2.5.8) and the 36px
        taskbar entry. These stay literal deliberately. A token invites a
        re-skin to lower an accessibility floor, which is the one move this
        theme should make awkward rather than easy.
     2. Injected-glyph boxes: 14px, 16px and 20px. These size the SVGs
        shell.js draws and are intrinsic to those drawings, not design
        values a consumer would retheme.
     3. Layout mechanism: 100vh on the desktop, -9999px parking the skip
        link, the 200px taskbar-entry cap.
     4. The 767px breakpoint in two media queries, which cannot be a
        custom property because media query conditions do not read them.

   game-console-ui-theme is the same claim audited to a tighter end state
   (one px literal, the skip link, plus its breakpoint), and is the model if
   you want this file's exceptions reduced further.

   Requires tokens.css. Load it, then components.css, then this, then the
   script:

     <link rel="stylesheet" href="tokens/tokens.css" />
     <link rel="stylesheet" href="components/components.css" />
     <link rel="stylesheet" href="shell/shell.css" />
     <script src="shell/shell.js" defer></script>
   ========================================================================== */

/* --------------------------------------------------------------------------
   DESKTOP, AND THE WALLPAPER SYSTEM
   The body is the desktop, and the desktop ground is a wallpaper slot, not
   a color. Two axes stay independent: the register (tokens.css) says what
   colors exist, the wallpaper (a data attribute on body) says how the
   ground composes them. The defining commercial exemplar of this archetype
   runs the same architecture in production, independent skin and wallpaper
   attributes on the body, which is this collection's shells and registers
   thesis observed in the wild. This repo adopts the slot and the
   mechanism; every scene shipped here is original work.

   THE MECHANISM (class standard for shell themes):
     body[data-wallpaper="quiet"]  the default field, also what you get
                                   with no attribute at all, so the no-JS
                                   document carries it by plain CSS
     body[data-wallpaper="scene"]  reveals the inline SVG scene layer

   shell.js only toggles the attribute; every visual belongs to these
   attribute-scoped rules. The picker, like all behavior, exists only after
   enhancement.

   CONTRAST BY CONSTRUCTION. The wallpaper layer may paint ONLY with these
   tokens: chrome, ground, scene, surface, surface-muted, border, and
   fill-warning. The darkest of those is the border stroke, and full ink
   over it measures 9.88:1, so the worst text-over-wallpaper pairing on the
   desktop (icon labels, full ink) clears AA more than twice over without a
   scrim. Measured over every allowed paint: surface 17.51, chrome 15.95,
   ground 15.13, surface-muted 14.57, fill-warning 14.48, scene 12.94,
   border 9.88. The sticky note and all window chrome are opaque surfaces
   with their own measured pairings, so the wallpaper cannot reach them.
   The class guarantee, should a future variant want darker paint: a
   token-driven scrim layer between wallpaper and content, with the ratios
   re-measured through the scrim. No shipped variant needs one.

   The quiet field: a soft top light (chrome fading into ground) under the
   dot grid. Both layers are decorative texture; nothing depends on seeing
   either.
   -------------------------------------------------------------------------- */
.os-desktop-ground {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--os-ground);
  background-image:
    radial-gradient(
      var(--os-scene) var(--os-scene-dot),
      transparent var(--os-scene-dot)
    ),
    linear-gradient(180deg, var(--os-chrome), var(--os-ground) 42%);
  background-size:
    var(--os-scene-gap) var(--os-scene-gap),
    100% 100%;
  color: var(--os-ink);
  font-family: var(--os-font-sans);
  font-size: var(--os-text-body);
  line-height: var(--os-leading-body);
}

/* The scene layer: an inline SVG in the page markup, because an SVG loaded
   through an image or a data URI is an isolated document that cannot read
   custom properties. Inlined, the paint rules below reach it and a re-skin
   of tokens.css re-skins the wallpaper with everything else. Hidden until
   the body attribute asks for it, so it costs the no-JS document nothing
   but bytes. */
.os-wallpaper {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.os-wallpaper > svg {
  width: 100%;
  height: 100%;
}

body[data-wallpaper="scene"] .os-wallpaper {
  display: block;
}

/* Wallpaper paint rules. Only the allowed tokens from the contrast
   contract above. */
.os-wallpaper .os-art-line {
  stroke: var(--os-border);
  fill: none;
}

.os-wallpaper .os-art-paper {
  fill: var(--os-surface);
  stroke: var(--os-border);
}

.os-wallpaper .os-art-shade {
  fill: var(--os-surface-muted);
  stroke: var(--os-border);
}

.os-wallpaper .os-art-sticky {
  fill: var(--os-fill-warning);
  stroke: var(--os-border);
}

/* Anyone who has asked for more contrast gets the quiet field regardless
   of what the picker says. Placed after the scene rule so it wins on
   source order at equal specificity. */
@media (prefers-contrast: more) {
  body[data-wallpaper] .os-wallpaper {
    display: none;
  }
}

/* The OS does not scroll; its windows do. Only when live: without the
   script the page is a page, and pages scroll. */
.os-live,
.os-live .os-desktop-ground {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}

/* --------------------------------------------------------------------------
   SKIP LINK
   Above everything, including the launcher panel.
   -------------------------------------------------------------------------- */
.os-skip {
  position: absolute;
  left: -9999px;
}

.os-skip:focus {
  left: var(--os-space-4);
  top: var(--os-space-4);
  z-index: var(--os-z-skip);
  padding: var(--os-space-3) var(--os-space-4);
  background: var(--os-surface);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius);
  box-shadow: var(--os-shadow-raised);
  font-weight: var(--os-weight-bold);
  color: var(--os-ink);
}

/* --------------------------------------------------------------------------
   BRAND CHIP
   The document header. As a page it is a normal masthead; live, it shrinks
   to a chip pinned to the top-left corner, the shell's fragment of a menu
   bar. The tagline is part of the page, not part of the OS, so the chip
   hides it.
   -------------------------------------------------------------------------- */
.os-brand {
  padding: var(--os-space-6) var(--os-space-4) 0;
}

.os-brand-name {
  margin: 0;
  font-size: var(--os-text-h2);
  font-weight: var(--os-weight-display);
  letter-spacing: var(--os-tracking-tight);
}

.os-brand-tagline {
  margin: var(--os-space-1) 0 0;
  color: var(--os-ink-muted);
  max-width: var(--os-measure);
}

.os-live .os-brand {
  position: fixed;
  top: var(--os-space-3);
  left: var(--os-space-3);
  z-index: var(--os-z-chrome);
  padding: var(--os-space-1) var(--os-space-3);
  background: var(--os-chrome);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius);
  box-shadow: var(--os-shadow-sm);
}

.os-live .os-brand-name {
  font-size: var(--os-text-sm);
}

.os-live .os-brand-tagline {
  display: none;
}

/* --------------------------------------------------------------------------
   ICONS
   A nav of anchor links. As a page: a wrapping grid under the header. Live
   on desktop: a vertical rail down the left edge, which is where four
   decades of desktops have kept them. Live on mobile the rail hides and the
   launcher panel (further down) shows the same nav instead.
   -------------------------------------------------------------------------- */
.os-icons {
  padding: var(--os-space-6) var(--os-space-4);
}

.os-icons-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--os-space-2);
}

.os-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--os-space-2);
  width: var(--os-icon-cell);
  padding: var(--os-space-2);

  text-decoration: none;
  text-align: center;
  border-radius: var(--os-radius);
  border: var(--os-border-width) solid transparent;
}

.os-icon:hover {
  background: var(--os-surface);
  border-color: var(--os-border);
}

.os-icon:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: var(--os-ring-offset);
}

/* The plate is the glyph's tile. Inline SVG inside, painted with
   currentColor so it reads the tokens like everything else. */
.os-icon-plate {
  display: grid;
  place-items: center;
  width: var(--os-icon-size);
  height: var(--os-icon-size);
  color: var(--os-ink);
  background: var(--os-surface);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius);
  box-shadow: var(--os-shadow-sm);
}

.os-icon-plate > svg {
  width: 60%;
  height: 60%;
}

.os-icon-label {
  font-size: var(--os-text-xs);
  font-weight: var(--os-weight-medium);
  color: var(--os-ink);
  line-height: var(--os-leading-snug);
}

.os-live .os-icons {
  position: fixed;
  top: var(--os-space-16);
  left: var(--os-space-3);
  padding: 0;
}

.os-live .os-icons-list {
  flex-direction: column;
}

/* --------------------------------------------------------------------------
   WINDOW, AS A PAGE
   Without the script a window is a card: chrome title bar, white body,
   strong outline, stacked in reading order with the page scrolling past.
   The [data-os-controls] slot stays empty; close and minimize are injected
   by shell.js because a control that does nothing must not exist.
   -------------------------------------------------------------------------- */
.os-window {
  max-width: var(--os-window-width);
  margin: 0 auto var(--os-space-8);

  display: flex;
  flex-direction: column;

  background: var(--os-surface);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius-window);
  box-shadow: var(--os-shadow-raised);
  overflow: hidden; /* the title bar's square corners clip to the radius */
}

.os-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--os-space-4);
  min-height: var(--os-titlebar-height);
  padding: 0 var(--os-space-2) 0 var(--os-space-4);

  background: var(--os-chrome);
  border-bottom: var(--os-border-width) solid var(--os-border);
}

.os-title {
  margin: 0;
  font-size: var(--os-text-sm);
  font-weight: var(--os-weight-bold);
  letter-spacing: var(--os-tracking-tight);
}

.os-controls {
  display: flex;
  gap: var(--os-space-1);
}

.os-control {
  display: grid;
  place-items: center;
  width: var(--os-control-size);
  height: var(--os-control-size);
  padding: 0;

  color: var(--os-ink);
  background: transparent;
  border: var(--os-border-width) solid transparent;
  border-radius: var(--os-radius-tight);
  cursor: pointer;
}

.os-control:hover {
  background: var(--os-surface-muted);
  border-color: var(--os-border);
}

.os-control:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: 0;
}

.os-control > svg {
  width: 14px;
  height: 14px;
}

.os-window-body {
  padding: var(--os-space-6);
  overflow-wrap: break-word;
}

.os-window-body > :first-child {
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   WINDOW, LIVE
   The same article, floating. Closed windows are display:none, which also
   removes them from the tab order; no aria-hidden bookkeeping is needed.
   Position: centered under the top edge, pushed by the cascade custom
   properties shell.js sets per window, clamped so a cascaded window cannot
   leave the viewport on a narrow screen.
   -------------------------------------------------------------------------- */
.os-live .os-window {
  display: none;
  position: fixed;
  left: clamp(
    var(--os-space-3),
    calc(50% - var(--os-window-width) / 2 + var(--os-dx, 0px)),
    calc(100% - var(--os-window-width) - var(--os-space-3))
  );
  top: calc(var(--os-space-12) + var(--os-dy, 0px));
  width: min(var(--os-window-width), calc(100vw - 2 * var(--os-space-3)));
  max-height: calc(
    100dvh - var(--os-taskbar-height) - var(--os-space-12) - var(--os-space-8)
  );
  margin: 0;
  z-index: var(--os-z-window);
}

.os-live .os-window.os-open {
  display: flex;
  animation: os-window-in var(--os-duration) var(--os-ease);
}

/* Only the top window casts the deep shadow; the rest of the pile sits
   lower. The top window's title bar also carries the accent hairline, which
   is where the accent spends part of its budget. */
.os-live .os-window.os-top {
  box-shadow: var(--os-shadow-window);
}

.os-live .os-window.os-top > .os-titlebar {
  box-shadow: inset 0 2px 0 0 var(--os-accent);
}

/* The window receives focus when opened. The ring only draws for keyboard
   users; a mouse click on an icon should not outline the whole window. */
.os-live .os-window:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: var(--os-ring-offset);
}

/* The body is the scroll region. shell.js gives it tabindex="0" so a
   keyboard user can scroll a long window without a pointer. */
.os-live .os-window-body {
  overflow-y: auto;
}

@keyframes os-window-in {
  from {
    opacity: 0;
    transform: translateY(var(--os-open-rise)) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   TASKBAR
   Two lists on one strip, and the distinction between them is the whole
   design. The shortcuts are a fixed text index of every window, identical
   with the script and without it. The tasklist is shell.js's, rebuilt on
   every change to track what is open. A real taskbar has always carried
   both, a pinned strip beside the running windows, so this is the
   desktop's own answer rather than a bolted-on navbar.

   WHY BOTH, AND WHAT WAS WRONG BEFORE. The static list used to be the
   tasklist, and enhancement destroyed it: with the script running, the
   only complete set of text links to this site vanished and was replaced
   by whatever happened to be open. That is correct desktop behaviour and
   it was also the entire second navigation surface disappearing exactly
   for the readers who have JavaScript. Class decision 44 in
   docs/class-decisions.md is the rule; this is the pilot complying with
   it. The shortcuts survive by construction: shell.js has no selector
   that reaches them.

   ONE ROW WHEN LIVE, NEVER TWO. --os-taskbar-height is load-bearing:
   the live window height is calc(100dvh - that). A taskbar that wrapped to
   a second row would put the bottom edge of every window silently under
   the chrome, and nothing would fail while it happened. So live, the strip
   does not wrap; it scrolls, which is what a full taskbar has always done.
   As a page there is no such constraint and no fixed strip to overflow, so
   it wraps like the ordinary footer nav it is, and the document keeps its
   promise not to scroll sideways. This is class decision 45, which this
   strip is what filed.
   -------------------------------------------------------------------------- */
.os-taskbar {
  background: var(--os-chrome);
  border-top: var(--os-border-width) solid var(--os-border-strong);
}

.os-taskbar-inner {
  display: flex;
  align-items: center;
  gap: var(--os-space-2);
  min-height: var(--os-taskbar-height);
  padding: var(--os-space-2) var(--os-space-3);
}

/* Both lists live in here; the clock sits outside it so it never scrolls
   away from the corner it belongs in. */
.os-taskbar-scroll {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--os-space-2);
}

.os-shortcuts,
.os-tasklist {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--os-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Live, the strip becomes one scrolling line. The vertical padding here is
   not spacing, it is the room the focus ring needs: an overflow container
   clips at its padding edge, the entries are nearly the full height of the
   strip, and without the reservation a ring would be shaved top and bottom.
   It is written as the ring's own tokens so it stays correct if the ring
   gets thicker, and it replaces .os-taskbar-inner's vertical padding rather
   than adding to it, which is what keeps the strip at the token height. */
.os-live .os-taskbar-inner {
  flex-wrap: nowrap;
  padding-top: 0;
  padding-bottom: 0;
}

.os-live .os-taskbar-scroll {
  flex-wrap: nowrap;
  padding: calc(var(--os-ring-width) + var(--os-ring-offset));
  overflow-x: auto;
}

.os-live .os-shortcuts,
.os-live .os-tasklist {
  flex: none;
  flex-wrap: nowrap;
}

/* The divider earns its place only when there is something on both sides of
   it. Empty is the no-JS state and the nothing-open state, and a rule
   floating at the end of the shortcuts would be dividing one list from
   nothing. */
.os-tasklist:not(:empty) {
  padding-left: var(--os-space-2);
  border-left: var(--os-border-width) solid var(--os-border-strong);
}

/* --------------------------------------------------------------------------
   THE SHORTCUTS
   Flat text, deliberately. The task entries beside them are raised pills
   with a surface and an outline, because they stand for objects that exist
   right now; a shortcut stands for a place you can go. Two lists of
   identical pills on one strip would be the more serious failure of the
   two: a reader would have no way to tell which entries are windows that
   are open and which are windows that are not.

   Contrast, measured against the chrome the strip sits on: full ink 15.95:1,
   and 14.57:1 on the hover fill. Muted ink was the first instinct for a
   chrome strip and measures 6.73:1, which clears AA and still reads as a row
   of disabled menus. Full ink.
   -------------------------------------------------------------------------- */
.os-shortcut {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: var(--os-space-1) var(--os-space-2);

  font-family: var(--os-font-sans);
  font-size: var(--os-text-sm);
  font-weight: var(--os-weight-medium);
  color: var(--os-ink);
  text-decoration: none;
  white-space: nowrap;

  border-radius: var(--os-radius);
}

.os-shortcut:hover {
  background: var(--os-surface-muted);
}

.os-shortcut:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: var(--os-ring-offset);
}

.os-task {
  display: inline-flex;
  align-items: center;
  gap: var(--os-space-2);
  min-height: 36px;
  max-width: 200px;
  padding: var(--os-space-1) var(--os-space-3);

  font-family: var(--os-font-sans);
  font-size: var(--os-text-sm);
  font-weight: var(--os-weight-medium);
  color: var(--os-ink);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;

  background: var(--os-surface);
  border: var(--os-border-width) solid var(--os-border);
  border-radius: var(--os-radius);
  cursor: pointer;
}

.os-task:hover {
  border-color: var(--os-border-strong);
}

.os-task:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: var(--os-ring-offset);
}

/* The top window's taskbar entry. aria-pressed carries the state; this
   rule just makes it visible. */
.os-task[aria-pressed="true"] {
  background: var(--os-surface-muted);
  border-color: var(--os-border-strong);
  box-shadow: inset 0 -2px 0 0 var(--os-accent);
}

/* A minimized window's entry: still present, visibly parked. */
.os-task.os-task-min {
  color: var(--os-ink-muted);
  background: var(--os-chrome);
}

/* A tray control: the taskbar's small square buttons, currently just the
   wallpaper picker shell.js injects. Sized like a window control but it
   lives in the chrome strip. */
.os-tray-btn {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--os-control-size);
  height: var(--os-control-size);
  padding: 0;

  color: var(--os-ink);
  background: transparent;
  border: var(--os-border-width) solid transparent;
  border-radius: var(--os-radius-tight);
  cursor: pointer;
}

.os-tray-btn:hover {
  background: var(--os-surface-muted);
  border-color: var(--os-border);
}

.os-tray-btn:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: 0;
}

.os-tray-btn > svg {
  width: 16px;
  height: 16px;
}

.os-clock {
  flex: none;
  font-family: var(--os-font-mono);
  font-size: var(--os-text-sm);
  color: var(--os-ink-muted);
  padding: 0 var(--os-space-2);
}

.os-live .os-taskbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--os-z-chrome);
}

/* --------------------------------------------------------------------------
   LAUNCHER (mobile chrome)
   The button is injected by shell.js, exists only live, and only shows on
   narrow screens. It toggles the icon nav, restyled as a panel that drops
   from the top corner. Same nav element, third costume.
   -------------------------------------------------------------------------- */
.os-launcher {
  display: none;
  position: fixed;
  top: var(--os-space-3);
  right: var(--os-space-3);
  z-index: var(--os-z-launcher);

  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;

  color: var(--os-ink);
  background: var(--os-chrome);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius);
  box-shadow: var(--os-shadow-sm);
  cursor: pointer;
}

.os-launcher:focus-visible {
  outline: var(--os-ring-width) solid var(--os-ring);
  outline-offset: var(--os-ring-offset);
}

.os-launcher > svg {
  width: 20px;
  height: 20px;
}

/* --------------------------------------------------------------------------
   THE RESPONSIVE METAPHOR-TRANSFORM
   Under 768px the desktop metaphor would be a diorama: a floating window on
   a phone is chrome for its own sake. So the metaphor transforms rather
   than shrinks. The window goes full-bleed above the taskbar, the icon rail
   becomes a launcher panel behind a top-corner button, and the taskbar
   stays, because a phone still wants to know what is open. This transform
   is part of the archetype, not a degradation of it.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
  .os-live .os-window {
    left: 0;
    top: 0;
    width: 100%;
    max-height: calc(100dvh - var(--os-taskbar-height));
    height: calc(100dvh - var(--os-taskbar-height));
    border-radius: 0;
    border-left: none;
    border-right: none;
    border-top: none;
  }

  /* Full-bleed windows have no visible pile, so the rise animation reads
     as a page sliding in; keep it, it is the same single motion. */

  /* The launcher button floats over the window's top-right corner, which
     is where the title bar keeps its controls. The title bar steps aside
     by exactly the launcher's footprint. */
  .os-live .os-titlebar {
    padding-right: calc(44px + var(--os-space-3) + var(--os-space-2));
  }

  /* A full-bleed window and a floating brand chip would fight over the top
     edge. The chip yields; the product's name lives in the title bar of
     whatever is open and in the document title. */
  .os-live .os-brand {
    display: none;
  }

  .os-live .os-launcher {
    display: inline-flex;
  }

  /* The rail hides; the same nav re-hangs as a panel under the launcher
     button when shell.js sets os-open on it. */
  .os-live .os-icons {
    display: none;
    top: calc(var(--os-space-3) + 44px + var(--os-space-2));
    left: auto;
    right: var(--os-space-3);
    z-index: var(--os-z-launcher);

    padding: var(--os-space-2);
    background: var(--os-surface);
    border: var(--os-border-width) solid var(--os-border-strong);
    border-radius: var(--os-radius-window);
    box-shadow: var(--os-shadow-window);
  }

  .os-live .os-icons.os-open {
    display: block;
    animation: os-window-in var(--os-duration) var(--os-ease);
  }
}

/* --------------------------------------------------------------------------
   THE STICKY NOTE
   The register's one sticker-class object: a paper note on the desktop,
   tilted by a token. As a page it sits in the document flow like an aside,
   because that is what it is. Live on desktop it sticks to the bottom-right
   of the desktop, under the window pile, above the taskbar. On mobile there
   is no visible desktop to stick it to, so it stays hidden rather than
   floating over content.
   -------------------------------------------------------------------------- */
.os-note {
  width: var(--os-note-size);
  margin: 0 auto var(--os-space-8);
  padding: var(--os-space-4);

  background: var(--os-fill-warning);
  border: var(--os-border-width) solid var(--os-border-strong);
  border-radius: var(--os-radius-tight);
  box-shadow: var(--os-shadow-sm);
  transform: rotate(var(--os-tilt-1));

  font-size: var(--os-text-sm);
  line-height: var(--os-leading-snug);
  color: var(--os-ink);
}

.os-live .os-note {
  position: fixed;
  right: var(--os-space-6);
  bottom: calc(var(--os-taskbar-height) + var(--os-space-6));
  margin: 0;
  z-index: calc(var(--os-z-window) - 1);
}

@media (max-width: 767px) {
  .os-live .os-note {
    display: none;
  }
}
