/**
 * The operating system: a taskbar, a desktop, and one screen over it.
 *
 * Three rules hold this together.
 *
 * **The desktop is the village.** It is not a route you navigate to; it fills
 * everything under the taskbar and it is always there. The picture is contained
 * rather than cropped, deliberately: every place on it is a button somebody has
 * to be able to press, and covering the viewport would push the places at the
 * edges off screen on a tall window.
 *
 * **One screen at a time.** No window manager — screens are routes, and a route
 * change replaces what is on the desktop rather than adding to it. That keeps
 * focus, history and the back button working the way they already do.
 *
 * **Desktop only, deliberately.** The rail this replaces was a left column above
 * 768px and a bottom tab bar below it — three layouts to keep in step. A phone
 * is getting an entirely different shell of its own instead, so there is nothing
 * responsive here: no breakpoints, no compact branch, no half-answer maintained
 * against a design that has not been drawn yet. A narrow window gets the desktop,
 * cramped, and that is the honest result rather than a disguised one.
 */

@layer layout {
  /**
   * How the stone is cut.
   *
   * Here rather than in tokens.css because the jewel belongs to the shell, and
   * the shell is the thing being designed — a direction that wants a ruby
   * rather than an emerald changes these five and nothing else. They are named
   * for the faces of a cut gem so that "which one is the dark side" is a
   * question the names answer.
   */
  :root {
    --jewel-table: light-dark(#5ec8a8, #9df3d8);
    --jewel-crown: light-dark(#1c7f68, #6fe0c0);
    --jewel-crown-dim: light-dark(#12604e, #3fb597);
    --jewel-pavilion: light-dark(#14705a, #4fc9a9);
    --jewel-pavilion-deep: light-dark(#0c4d3e, #2a8f76);

    /**
     * The bevel.
     *
     * Four faces around a flat one, mitred at the corners — the same idea as the
     * jewel's cut, in a rectangle. It needs no gradients and no images: a plain
     * CSS border already joins its four sides on the diagonal, so giving each
     * side its own colour *is* the bevel, and the corners come out chamfered for
     * free.
     *
     * Lit from the top left throughout, which is the convention every bevelled
     * interface has used since they were drawn a pixel at a time. Which pairing
     * is which decides the direction: near faces dark reads as sunken, near
     * faces light reads as raised. The window frame is raised, the well inside
     * it is sunken, and both come from these same five values.
     */
    --bevel-width: 2px;
    --bevel-near: light-dark(#b9c6c1, #0d1418);
    --bevel-near-side: light-dark(#c8d3ce, #131c21);
    --bevel-far-side: light-dark(#f3f7f5, #2f4048);
    --bevel-far: light-dark(#ffffff, #3a4d56);
    --bevel-face: light-dark(#e7edea, #162025);

    /* The bar and the objects on it. Dark in both themes: a taskbar is the one
       piece of chrome that stays constant so the desktop behind it can change. */
    --bar-face: light-dark(#20262b, #12181c);
    --bar-edge: light-dark(#161b1f, #0a0e11);
    --bar-item: light-dark(#2b3238, #1c242a);
    --bar-item-edge: light-dark(#3a434a, #2b353c);
    --bar-text: light-dark(#d8e0e4, #b9c6cd);

    /* The title bar of a window. Two stops and the text that sits on them. */
    --title-a: light-dark(#2f6fb5, #1d4f86);
    --title-b: light-dark(#1f5da3, #143a63);
    --title-text: #ffffff;
  }

  .os {
    display: grid;
    grid-template-rows: auto 1fr;
    /* dvh rather than vh: the mobile toolbar collapses and vh does not notice. */
    height: 100dvh;
    overflow: hidden;
  }

  /* ---- the taskbar ---- */

  /**
   * The bar: flat, dark, and out of the way.
   *
   * It used to carry a glass tint and a backdrop blur, which is a phone-OS
   * translucency — the bar borrowed whatever was behind it and never settled on
   * a colour of its own. A desktop taskbar is a solid strip with one hairline
   * along its edge, and everything on it is a distinct object sitting on that
   * strip rather than embedded in it.
   */
  .taskbar {
    z-index: var(--z-nav);
    display: flex;
    gap: var(--space-3);
    align-items: center;
    justify-content: space-between;
    height: 48px;
    padding: 0 var(--space-2);
    background: var(--bar-face);
    border-bottom: 1px solid var(--bar-edge);
  }

  /* The corner the menu hangs from. Relative so the menu can be positioned
     against it rather than against the viewport. */
  .taskbar__corner {
    position: relative;
    display: flex;
    flex: 0 0 auto;
    align-items: stretch;
  }

  /**
   * Start.
   *
   * The largest thing on the bar and the only one in the accent colour, because
   * it is the one control that is not a route — it opens everything else. A
   * gradient down its face and a soft shadow under it give it the standing the
   * reference's Menu button has; every other object on the bar is flat and dark
   * by comparison, which is what makes this one read as primary.
   *
   * The stone stays, but as the button's icon rather than as the button. Its
   * glow and its specular bloom are gone: those belonged to a polished gem
   * floating on glass, and this bar is neither.
   */
  .start {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    height: 34px;
    padding: 0 var(--space-4) 0 var(--space-2);
    font: 600 var(--text-sm) / 1 var(--font-ui);
    color: var(--accent-ink);
    cursor: pointer;
    background: linear-gradient(
      to bottom,
      light-dark(#3fa88c, #8aeacd),
      var(--accent)
    );
    border: 1px solid var(--accent);
    border-radius: var(--radius-md);
    box-shadow: var(--edge-lit), 0 1px 3px var(--shadow-key);
    transition: filter var(--motion-press), transform var(--motion-press);
  }

  .start:hover {
    filter: brightness(1.08);
  }

  .start:active,
  .start--open {
    filter: brightness(0.94);
    transform: translateY(1px);
    box-shadow: var(--edge-well);
  }

  .start:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .start__label {
    letter-spacing: 0.01em;
  }

  /* ---- the stone, now an icon ---- */

  .stone {
    position: relative;
    display: block;
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    /* A cut stone seen from above: six sides, point up. */
    clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
  }

  /**
   * The cut.
   *
   * A conic gradient with hard stops is exactly a faceted crown seen from
   * overhead — each wedge is one facet, and the alternation between light and
   * dark is the light raking across them. One background rather than six
   * elements, so the facets cannot drift out of alignment with the silhouette.
   */
  .stone__cut {
    position: absolute;
    inset: 0;
    background:
      conic-gradient(
        from 210deg,
        var(--jewel-table) 0deg 60deg,
        var(--jewel-crown) 60deg 120deg,
        var(--jewel-pavilion) 120deg 180deg,
        var(--jewel-pavilion-deep) 180deg 240deg,
        var(--jewel-crown-dim) 240deg 300deg,
        var(--jewel-crown) 300deg 360deg
      );
  }

  /* One flat highlight. Hard-edged rather than a soft bloom: the bloom was what
     made the stone look lit from a different world than the bar it sits on. */
  .stone__shine {
    position: absolute;
    top: 14%;
    left: 16%;
    width: 34%;
    height: 26%;
    background: light-dark(rgb(255 255 255 / 80%), rgb(255 255 255 / 70%));
    clip-path: polygon(0% 0%, 100% 0%, 55% 100%);
  }

  /* ---- the menu ---- */

  .start-menu {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: var(--z-toast);
    display: grid;
    gap: var(--space-2);
    width: 248px;
    padding: var(--space-2);
    /* The OS clips its own overflow, so a menu taller than the window would be
       cut off with no way to reach the rest of it. It scrolls instead. */
    max-height: calc(100dvh - var(--space-8));
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
  }

  .start-menu__who {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2);
    background: var(--surface-sunken);
    border-radius: var(--radius-sm);
  }

  .start-menu__handle {
    overflow: hidden;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .start-menu__group {
    display: grid;
    gap: 2px;
  }

  .start-menu__group--system {
    padding-top: var(--space-2);
    border-top: 1px solid var(--border);
  }

  .start-item {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    width: 100%;
    padding: var(--space-2);
    font: 500 var(--text-sm) / 1.2 var(--font-ui);
    color: var(--text);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
  }

  .start-item:hover,
  .start-item:focus-visible {
    background: var(--surface-raised);
  }

  .start-item--open {
    color: var(--accent);
  }

  .start-item__icon {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 24px;
    height: 24px;
    background: var(--surface-sunken);
    border-radius: var(--radius-full);
  }

  /**
   * The row of tasks.
   *
   * No tray. It had one — a sunken bevelled well with the tasks inside it — and
   * that made the row read as a toolbar embedded in the bar rather than as
   * objects sitting on it. Each task carries its own edge instead, which is what
   * a taskbar actually looks like. The mitred bevel is not wasted: it moves to
   * the windows, where a moulded frame is the whole idea.
   *
   * Left-aligned, beside Start. Centred navigation is a website; a taskbar
   * fills from the corner it starts in and leaves the rest of the bar empty.
   */
  .taskbar__routes {
    display: flex;
    flex: 0 1 auto;
    gap: var(--space-1);
    align-items: center;
    min-width: 0;
  }

  .taskbar__tray {
    display: flex;
    flex: 0 0 auto;
    gap: var(--space-2);
    align-items: center;
  }

  /* ---- a task: one route, open or not ---- */

  /**
   * A task: one route, pinned, and lit while it is the one open.
   *
   * Its own small dark key with its own thin edge — an object on the bar rather
   * than a region of it. The width is capped so six cannot push the tray off the
   * end; a long label truncates the way a window title does in any taskbar.
   */
  .task {
    display: flex;
    flex: 0 1 auto;
    gap: var(--space-2);
    align-items: center;
    min-width: 0;
    max-width: 168px;
    height: 32px;
    padding: 0 var(--space-3);
    font: 500 var(--text-sm) / 1.2 var(--font-ui);
    color: var(--bar-text);
    text-decoration: none;
    white-space: nowrap;
    background: var(--bar-item);
    border: 1px solid var(--bar-item-edge);
    border-radius: var(--radius-sm);
    transition:
      background-color var(--motion-fast),
      border-color var(--motion-fast),
      color var(--motion-fast);
  }

  .task:hover {
    color: light-dark(#ffffff, #e8f1f5);
    border-color: var(--accent);
  }

  /* Open reads as the accent taking the whole key. With each task already
     carrying an edge, an underline indicator would be a second border on a
     button that has one. */
  .task--open {
    color: var(--accent-ink);
    background: var(--accent);
    background-image: var(--gloss-soft);
    border-color: var(--accent);
  }

  .task__icon {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 20px;
    height: 20px;
    font-size: var(--text-sm);
  }

  .task__label {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ---- the desktop ---- */

  /**
   * Everything below the taskbar.
   *
   * The desktop and the screen over it are siblings rather than parent and
   * child, because the desktop's root belongs to `mountDesktop` alone — a screen
   * rendered inside it would be swept away the moment the village re-rendered.
   * So they share this box instead, and it is what a screen insets against.
   * Without it a screen positions itself against the viewport and covers the
   * taskbar, which is exactly what it did the first time.
   */
  .os__stage {
    position: relative;
    overflow: hidden;
  }

  .desktop {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    overflow: hidden;
    /* So the frame below can ask how much room there is on both axes. The
       element already has a definite size from `inset: 0`; this is what lets a
       descendant read it. */
    container-type: size;
    /* Set from the village's own palette by mountDesktop, so the ground around
       the picture is the same green as the ground in it. The fallback matters:
       until the map has loaded there is no palette to ask. */
    background: var(--desktop-ground, var(--surface-sunken));
  }

  /* The horizon: where the picture stops and the ground around it carries on. */
  .desktop__frame::after {
    position: absolute;
    inset: 0;
    pointer-events: none;
    content: '';
    box-shadow: 0 0 0 1px var(--desktop-edge, var(--border-strong));
  }

  /*
   * The picture, contained rather than cropped.
   *
   * Both axes have to bind, and whichever runs out first decides the size —
   * which is arithmetic CSS can only do when the two are in the same unit. The
   * container query units are that: `100cqw` is the room across, `100cqh` the
   * room down, and the smaller of the width we would like and the width the
   * available height allows is the width the picture gets.
   *
   * `width: 100%` was what this used to say, and it cropped. The frame took the
   * full width, the canvas's own ratio made it taller than the space under the
   * taskbar, and `.desktop` clipped the difference — a hundred and sixty pixels
   * of the bottom of the world at 1280×720. `max-height: 100%` was there and
   * did nothing: the grid row it measured against is auto-sized, so it had
   * already grown to fit the very thing it was supposed to be limiting.
   */
  .desktop__frame {
    position: relative;
    width: min(100cqw, calc(100cqh * var(--desktop-aspect, 1.6)));
    aspect-ratio: var(--desktop-aspect, 1.6);
    line-height: 0;
  }

  .desktop__art {
    display: block;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
  }

  .desktop__places {
    position: absolute;
    inset: 0;
    padding: 0;
    margin: 0;
    list-style: none;
  }

  .place {
    position: absolute;
  }

  .place__hit {
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    cursor: pointer;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
  }

  .place__hit:hover,
  .place__hit:focus-visible {
    border-color: var(--accent);
  }

  /* The name is for the screen reader and for a pointer that stops on it. The
     picture already says where everything is. */
  .place__name {
    position: absolute;
    bottom: 100%;
    left: 50%;
    padding: 2px var(--space-2);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--text);
    white-space: nowrap;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    opacity: 0;
    transform: translateX(-50%);
    transition: opacity var(--motion-fast);
  }

  .place__hit:hover .place__name,
  .place__hit:focus-visible .place__name {
    opacity: 1;
  }

  /* ---- a window, over the desktop ---- */

  /**
   * The frame.
   *
   * A raised bevel: the same five values the tray uses, with the pairs swapped
   * so the near faces catch the light and the far ones fall away. That one
   * reversal is the whole difference between a thing cut into a surface and a
   * thing standing on it, and it is why the bevel is stated as five colours and
   * a direction rather than as two separate palettes.
   */
  .window {
    position: absolute;
    inset: var(--space-5) var(--space-4);
    display: grid;
    grid-template-rows: auto 1fr;
    max-width: var(--content-max);
    margin: 0 auto;
    overflow: hidden;
    background: var(--bevel-face);
    border-style: solid;
    border-width: var(--bevel-width);
    border-top-color: var(--bevel-far);
    border-left-color: var(--bevel-far-side);
    border-right-color: var(--bevel-near-side);
    border-bottom-color: var(--bevel-near);
    border-radius: 4px;
    box-shadow: 0 4px 18px var(--shadow-key);
    /* Where it has been dragged to. Written by wireWindowDrag through the
       CSSOM, because the CSP drops a style attribute in markup. No transition
       on it: a transform that eases is a window that lags behind the hand. */
    transform: translate(var(--drag-x, 0px), var(--drag-y, 0px));
  }

  .window--dragging {
    user-select: none;
  }

  /* Maximised: the stage, less the margin that says a window is a window. */
  .window--maximised {
    inset: 0;
    max-width: none;
    border-radius: 0;
    /* Filling the stage means wherever it was dragged to stops applying. */
    transform: none;
  }

  .window__bar {
    cursor: grab;
    /* The browser must not claim the gesture for panning before we see it. */
    touch-action: none;
    display: flex;
    gap: var(--space-3);
    align-items: center;
    justify-content: space-between;
    padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
    background: linear-gradient(to right, var(--title-a), var(--title-b));
  }

  .window--dragging .window__bar {
    cursor: grabbing;
  }

  /**
   * The grip.
   *
   * The bar drags from anywhere, so this is not the only way to move a window —
   * it is the way that works without a pointer. A real button, so it can be
   * tabbed to, and its name gives both routes: drag it, or use the arrow keys.
   */
  .window__grip {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 20px;
    height: 22px;
    padding: 0;
    cursor: grab;
    background: transparent;
    border: 0;
    border-radius: 2px;
  }

  .window__grip:focus-visible {
    outline: 2px solid var(--title-text);
    outline-offset: -2px;
  }

  /* Dots: the usual sign that a thing is a handle. */
  .window__grip-dots {
    width: 7px;
    height: 14px;
    color: var(--title-text);
    background-image: radial-gradient(currentcolor 40%, transparent 42%);
    background-size: 3px 3px;
    opacity: 0.8;
  }

  .window__title {
    /* Takes the slack, so the title stays beside the grip instead of drifting
       to the middle of the bar once space-between has three things to spread. */
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--title-text);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .window__controls {
    display: flex;
    flex: 0 0 auto;
    gap: 2px;
  }

  /**
   * A control.
   *
   * Small, square and raised out of the title bar — the same lighting as the
   * frame, one size down. The glyphs are drawn rather than typed: a dash, a box
   * and a cross at these sizes are three fonts' worth of disagreement about
   * weight and baseline, and a border is always exactly a border.
   */
  .control {
    display: grid;
    place-items: center;
    width: 26px;
    height: 22px;
    padding: 0;
    cursor: pointer;
    background: var(--bevel-face);
    border-style: solid;
    border-width: 1px;
    border-top-color: var(--bevel-far);
    border-left-color: var(--bevel-far-side);
    border-right-color: var(--bevel-near-side);
    border-bottom-color: var(--bevel-near);
    border-radius: 2px;
  }

  .control:hover {
    filter: brightness(1.08);
  }

  /* Pressed: the light comes from the other side, which is the whole trick a
     bevelled interface uses to show a button going in. */
  .control:active,
  .control[aria-pressed='true'] {
    border-top-color: var(--bevel-near);
    border-left-color: var(--bevel-near-side);
    border-right-color: var(--bevel-far-side);
    border-bottom-color: var(--bevel-far);
  }

  .control--close:hover {
    background: var(--danger);
  }

  .control__glyph {
    display: block;
    background: currentcolor;
  }

  .control__glyph--minimise {
    width: 9px;
    height: 2px;
  }

  .control__glyph--maximise {
    width: 9px;
    height: 8px;
    background: transparent;
    border: 2px solid currentcolor;
    border-top-width: 3px;
  }

  /* Two bars crossed, so the × is the same weight as the other two glyphs. */
  .control__glyph--close {
    position: relative;
    width: 9px;
    height: 9px;
    background: transparent;
  }

  .control__glyph--close::before,
  .control__glyph--close::after {
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 2px;
    content: '';
    background: currentcolor;
  }

  .control__glyph--close::before { transform: rotate(45deg); }
  .control__glyph--close::after { transform: rotate(-45deg); }

  /**
   * The well.
   *
   * Sunken against the frame — near faces dark again — so the content reads as
   * inside something rather than printed on it. This is the panel the reference
   * puts its list in.
   */
  .window__body {
    margin: var(--space-1);
    overflow-y: auto;
    background: var(--surface);
    border-style: solid;
    border-width: 1px;
    border-top-color: var(--bevel-near);
    border-left-color: var(--bevel-near-side);
    border-right-color: var(--bevel-far-side);
    border-bottom-color: var(--bevel-far);
  }

  .window__body > * {
    padding: var(--space-4);
  }
}
