/* view3d.css -- how the render client looks, for every page that loads it.

   Lifted whole out of index.html when the viewer became a file of its own
   (view3d.js), and shared for the same reason: one copy of the bar, the
   switch, the sliders, the strip and the overlays, so a change to any of
   them is a change on both pages. A few rules in here belong to the main
   page alone -- the scene dropdown, the settings panel, the sign-in box --
   and they cost the show page nothing but the bytes: it has no such
   elements, so the selectors simply never match. */
  /* color-scheme:dark is not decoration: without it the OS draws the popup of
     the <select> below in its light theme, while color:inherit hands it the
     pale ink of this page, and the list comes out white on white. */
  :root { --bg:#0c1417; --line:#22333a; --ink:#8fa3aa; --hot:#4fb3c0;
          --card:#101c21; --ex:#6ecf8b; --pv:#e0b25a; --warn:#ff9a8a;
          color-scheme:dark; }
  * { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
  html,body { margin:0; height:100%; background:var(--bg); color:#dce7ea;
              font:14px/1.45 system-ui,-apple-system,Segoe UI,sans-serif; }
  body { display:flex; flex-direction:column; overflow:hidden; }
  /* Two rows: the scene and the two things that act on it, then the camera
     controls. The rows are fixed and only the scene name may be squeezed. */
  header { display:flex; flex-direction:column; gap:6px; padding:8px 12px;
           border-bottom:1px solid var(--line); flex:0 0 auto; }
  .bar { display:flex; gap:8px; align-items:center; min-width:0; }
  .bar > .sp { flex:1 1 auto; }
  select,button,input { background:#13212636; color:inherit; font:inherit;
                        border:1px solid var(--line); border-radius:7px;
                        padding:6px 10px; }
  /* The popup list is drawn by the OS, not by this page: it ignores the
     translucent background above and needs an opaque one of its own. The
     group headings need it too and do not take it from the options -- left
     alone, Chrome draws them as dark text on a pale strip of its own, and
     any colour put on them lands on that. */
  option { background:#0e171a; color:#dce7ea; }
  optgroup { background:#0e171a; color:var(--ink); font-style:normal;
             font-weight:600; }
  button { cursor:pointer; white-space:nowrap; }
  button[aria-pressed=true] { border-color:var(--hot); color:var(--hot); }
  button:disabled { opacity:.4; cursor:default; }
  button:disabled:active { transform:none; }
  /* Everything below this line is "the pointer is on it" behaviour, and a
     touch screen has no pointer: it sets :hover on the last thing tapped and
     leaves it there until something else is tapped. So the pressed shift only
     applies where the device can actually hover -- the rest of the time the
     buttons stay in their resting state, which is what they look like on a
     phone anyway. */
  @media (hover: hover) and (pointer: fine) {
    button:active { transform:translateY(1px); }
  }
  /* Focus is the keyboard's version of hover and it is not a touch thing, so
     it stays outside the media query: tabbing to a button should show it. */
  button:focus-visible { outline:2px solid var(--hot); outline-offset:2px; }
  /* The scene name is the only thing here that may be squeezed: the buttons
     are targets for a thumb and the gear must never be the one pushed off. */
  #scene { flex:1 1 auto; min-width:3em; }
  /* Several ways of drawing one scene, so one control with several parts
     rather than buttons that happen to be next to each other: the parts are
     alternatives, and a switch is the shape that says so. The chosen one is
     filled, not outlined -- an outline is what every other button already
     wears. AI is one of those parts, so the splats are chosen the way every
     other way of drawing this scene is. */
  .switch { display:inline-flex; flex:0 0 auto; padding:3px; gap:3px;
            border:1px solid var(--line); border-radius:999px;
            background:#13212636; }
  .switch button { border:0; background:transparent; color:var(--ink);
                   border-radius:999px; padding:6px 14px; }
  .switch button[aria-pressed=true] { background:var(--hot); color:#06222a;
                                      font-weight:600; }
  .switch button:disabled { opacity:.45; }
  #modes { flex:0 0 auto; }
  /* The row's buttons carry an icon and a word. On a desktop both; on a
     phone the icon alone, and the focus slider gives up its width to
     whatever the others need, so the row stays one line (see the phone
     block below). The icons are the frames button's kind: a stroke in the
     text colour, so they take the pressed accent with the word. */
  .ib { display:inline-flex; align-items:center; gap:6px; }
  .ib svg, .knob svg { display:block; width:18px; height:18px; flex:0 0 auto; }
  /* The AI mode sits in the modes switch and is the one mode that may not be
     there yet, so its icon is its state: one of four, picked by data-state.
     The word stays "AI" throughout -- it is a mode, and the other modes are
     one word each -- and the icon is what says whether it can be drawn,
     is being made, or is waiting its turn. The training one turns, because
     training is the state that lasts for half an hour. */
  #ai svg { display:none; }
  #ai[data-state=request] svg.request, #ai[data-state=queued] svg.queued,
  #ai[data-state=training] svg.training, #ai[data-state=ready] svg.ready { display:block; }
  #ai[data-state=training] svg { animation:ai_turn 2.4s linear infinite; }
  @keyframes ai_turn { to { transform:rotate(360deg); } }
  /* A mode button, so it wears the switch's shape; the icon is the only
     thing it has that the others do not, and inline-flex is what keeps the
     two of them on one line -- the icons are display:block, and in a plain
     button that is a line of its own. */
  #ai { display:inline-flex; align-items:center; gap:5px; }
  /* No model yet: the one part of the switch that is not a way of drawing
     this scene but a piece of work to ask for. In the page's warning colour,
     so it reads as different from its neighbours at a glance rather than
     after the title is read -- and the icon is a stroke in currentColor, so
     it comes along. The states in between (queued, training) stay ordinary:
     the work is already happening and there is nothing to notice. */
  #ai[data-state=request] { color:var(--warn); }
  #ai[hidden] { display:none; }
  #ai svg { width:16px; height:16px; }
  /* The focus slider, rays mode only: an icon, the word, the range. The
     range keeps the browser's own thumb and track in the page's accent --
     a restyled slider on a phone is a slider nobody can grab. */
  .knob { display:inline-flex; align-items:center; gap:6px; flex:0 0 auto;
          color:var(--ink); font-size:13px; cursor:pointer; }
  .knob input { width:120px; padding:0; border:0; background:none;
                accent-color:var(--hot); cursor:pointer; }
  .knob[hidden] { display:none; }
  #movelamp[hidden], #lampbar[hidden], #lampctl[hidden] { display:none; }
  #lampctl { gap:8px; }
  /* The lamp's row is six sliders and three buttons. One line holds them
     from about 1040 px up, which is a laptop's window open wide; narrower
     than that the tracks come out 60 px long and a 16-step size is a track
     nobody can set. So it breaks in two below 1040, and it breaks where it
     means something -- where the bulb stands on the first line, how it
     burns on the second. The knobs are handed to the bar itself
     (display:contents), or the break would land inside #lampctl and indent
     the second line under the bulb. */
  #lampbreak { display:none; }
  @media (max-width:1040px) {
    #lampbar { flex-wrap:wrap; row-gap:5px; }
    #lampctl { display:contents; }
    #lampbreak { display:block; flex:0 0 100%; height:0; }
    /* A small basis and the growing left to the slider: with wrapping on, an
       item is never squeezed under its basis first, so a 120 px one would
       take the row to three lines before any track had given up a pixel. */
    #lampbar .knob { flex:1 1 44px; min-width:0; }
    #lampbar .knob input { width:auto; flex:1 1 auto; min-width:44px; }
    #lampbar > .sp { flex:0 0 0; }      /* the slack is the sliders', not its */
  }
  /* Wider, it is the one line the bulb is on. The sliders give way there,
     from 120 px down to 56, rather than push the home button past the edge,
     which is what the flex:0 0 auto they wear elsewhere did. */
  @media (min-width:1041px) {
    #lampctl .knob { flex:0 1 auto; min-width:0; }
    #lampctl .knob input { width:auto; flex:1 1 120px; min-width:56px; }
  }
  #home,#gear { flex:0 0 auto; }
  /* The gear is a glyph and not a word: at the page's text size it comes out
     smaller than the letters on the buttons beside it, because the font draws
     it inside the line rather than filling it. So it gets a size of its own,
     with the line box pinned to the glyph and the padding made up to the same
     34px the other buttons in the row stand at. */
  #gear { font-size:20px; line-height:1; padding:6px 9px; }
  /* The examples in the dropdown, in green. The popup is drawn by the OS and
     not by this page, so this reaches a desktop's list and nothing else: a
     phone's picker takes the colour of an <option> as a suggestion and the
     label of an <optgroup> as decoration. So the word "example" is in the
     option's own text as well, where no stylesheet can be ignored -- and it
     goes right after the name, because that end of the line is the part a
     narrow select still shows. */
  #scene option.ex, #scene optgroup.ex { color:var(--ex); }
  /* This page's own scenes, in amber, the same way and with the same
     caveats. Nobody else's page lists them at all, so the colour is not a
     warning -- it is where to look for the room you made yesterday. */
  #scene option.pv, #scene optgroup.pv { color:var(--pv); }
  /* The closed select draws the chosen line in its own colour, not the
     option's, so the class goes on the select as well when an example is the
     one open. That line is what a phone shows all the time, and it is the one
     place the colour is certain to arrive there. */
  #scene.ex { color:var(--ex); border-color:var(--ex); }
  /* And, once one is open, the same green beside the scene, saying why the
     gear's edit and its remove are not there. It gives way on a phone:
     the select itself says "· example" there, in the bar, always. */
  #shipped { flex:0 0 auto; padding:2px 7px; border:1px solid var(--ex);
             border-radius:999px; color:var(--ex); font-size:12px;
             white-space:nowrap; }
  #shipped[hidden] { display:none; }
  @media (max-width:620px) { #shipped { display:none; } }
  /* Who this page is, pushed to the far end of the bar: the name in a chip
     and the way in and out beside it. Amber, which is the colour this page
     has always used for "yours and nobody else's". */
  #me { margin-left:auto; display:flex; gap:6px; align-items:center;
        flex:0 0 auto; }
  #acct { padding:2px 7px; border:1px solid var(--pv); border-radius:999px;
          color:var(--pv); font-size:12px; white-space:nowrap;
          max-width:12ch; overflow:hidden; text-overflow:ellipsis; }
  #acct[hidden] { display:none; }
  /* The camera, when there is nobody to own what it would make. Dimmed and
     not disabled: a button that does nothing and says nothing is how a person
     concludes the server is broken. It opens the sign-in box instead. */
  #photo.off { opacity:.45; }
  /* The sign-in box: the panel's materials, the middle of the stage, and no
     wider than the question. */
  #login { position:absolute; top:50%; left:50%; translate:-50% -50%;
           width:300px; max-width:calc(100% - 20px); background:var(--card);
           border:1px solid var(--line); border-radius:10px; padding:14px;
           z-index:6; }
  #login[hidden] { display:none; }
  #login h2 { margin:0 0 2px; font-size:13px; letter-spacing:.08em;
              text-transform:uppercase; color:var(--ink); }
  #li_msg { color:var(--warn); font-size:12px; min-height:16px; }
  /* The password and the eye that shows it, on one line. A typed password
     nobody can read is how somebody signs in wrong three times and concludes
     the account is gone -- on a phone keyboard most of all. */
  .pw { display:flex; gap:6px; align-items:center; }
  .pw input { flex:1 1 auto; }
  #li_eye { flex:0 0 auto; padding:5px 8px; line-height:0; }
  #li_eye svg { width:18px; height:18px; display:block; }
  #li_eye .slash { display:none; }
  #li_eye[aria-pressed="true"] .slash { display:inline; }
  /* The capture button is the one big target in the header: it is the thing
     a thumb reaches for on a phone when a new scene is to be shot. Photo, in
     the row below, is the other one -- that saves the picture on the screen
     and wears the ordinary .ib shape. */
  #photo { flex:0 0 auto; display:inline-flex; align-items:center;
           justify-content:center; gap:6px; height:40px; min-width:56px;
           padding:0 14px; }
  #photo svg { display:block; width:22px; height:22px; }
  #stat { color:var(--ink); font-size:12px; white-space:nowrap; }
  /* Asking for an AI model is answered at once and then says nothing for
     half an hour, and the button's own change -- to a greyed clock -- is easy
     to miss on a phone held at arm's length. So the press itself is visible:
     the button pulses for a moment, in the accent when it was taken and in
     red when it was refused, which is also what a busy server looks like
     here. Opacity is in the frames because the button is disabled by then,
     and a disabled button is drawn at 40%. */
  @keyframes ai_taken {
    50% { background:var(--hot); color:#06222a; border-color:var(--hot);
          opacity:1; }
  }
  @keyframes ai_refused {
    50% { border-color:var(--warn); color:var(--warn); opacity:1; }
  }
  #ai.taken { animation:ai_taken .45s ease-in-out 3; }
  #ai.refused { animation:ai_refused .45s ease-in-out 2; }
  /* The frames button shows and hides the strip below; its number is how many
     photographs the scene was built from. */
  /* The frames button lives in the top bar, beside the camera: the
     controls row below is the one that has to fit a phone in one line
     (2026-09-18), and the top bar's scene name is the thing that gives way. */
  #frames { display:inline-flex; align-items:center; gap:5px; flex:0 0 auto; }
  #frames svg { display:block; width:18px; height:18px; }
  @media (max-width:620px) {
    header { gap:5px; padding:6px 8px; }
    .bar { gap:6px; }
    /* A phone is 411 px wide and this row is about 520 of them once the AI
       photo button is in it, so it takes two lines rather than pushing the
       frames button off the edge. Only this row: the top one holds the scene
       name, which gives way by being squeezed, and wrapping it would put the
       camera and the gear on a line of their own for nothing. */
    .bar.wrap { flex-wrap:wrap; row-gap:5px; }
    select,button { padding:6px 8px; }
    .switch { padding:2px; }
    .switch button { padding:6px 10px; }
    #photo { height:36px; min-width:50px; padding:0 10px; }
    #photo svg { width:20px; height:20px; }
    #gear { font-size:18px; padding:6px 8px; }
    /* One line: words off, icons on, the slider takes what is left. The
       sums are tight -- at 360 px the fixed items come to about 340 with
       these paddings and 4 px gaps, and the slider gets its 40 px minimum;
       at 411 it gets ninety. */
    .bar.wrap { gap:4px; }
    .ib .tx, .knob .tx { display:none; }
    /* The AI mode keeps its word: it is two letters, the modes beside it are
       words, and a switch where one part is an icon alone reads as a
       different kind of control. */
    #ai .tx { display:inline; }
    .ib { padding:6px 5px; }
    /* a small flex basis, or the row wraps before the slider has shrunk:
       with wrapping on, an item is never squeezed under its basis first */
    .knob { flex:1 1 40px; min-width:0; gap:0; }
    .knob svg { display:none; }         /* the slider alone: the icon's width is the track's */
    .bar.wrap > .sp { flex:0 0 0; }     /* the slack is the slider's, all of it */
    .knob input { flex:1 1 auto; width:auto; min-width:36px; }
    .switch button { padding:5px 5px; font-size:12.5px; }
    #frames { padding:6px 6px; gap:3px; }
    #stat { display:none; }          /* the note line says it on a phone */
    /* The lamp's two lines (above) leave room for the words again, and they
       are what that row needed most: six sliders with the labels off are six
       identical tracks, and x is not something a thumb can guess from one. */
    #lampbar .tx { display:inline; }
    #lampbar .knob { gap:3px; }
  }

  /* The photographs the scene was built from: one strip under the picture,
     scrolled sideways, tapped to stand where that camera stood.

     A row of the page and not a layer over the render. The render is drawn to
     the stage's size, so a strip laid over it would hide pixels the server had
     just made, and would sit on the note and the error line that live down
     there; a row of its own only makes the picture a little shorter -- and, on
     a phone, a little cheaper to draw, which is where cheap matters.

     Tiles keep the photograph's own proportions rather than cropping every
     frame to one box: a portrait next to a landscape is what the series
     looks like, and the shape arrives with the scene, so the strip is laid
     out before a single picture has loaded and nothing shifts when they do.
     The height is one number, --th, and the screen size sets it: a phone
     gets a thumb-high strip, a desktop a legible one, a large screen more. */
  #strip { --th:96px; flex:0 0 auto; display:flex; align-items:center; gap:6px;
           padding:6px 8px; border-top:1px solid var(--line);
           background:var(--card); overflow-x:auto; overflow-y:hidden;
           overscroll-behavior-x:contain; scrollbar-width:thin;
           touch-action:pan-x; scroll-snap-type:x proximity; }
  #strip[hidden] { display:none; }
  #strip::-webkit-scrollbar { height:6px; }
  #strip::-webkit-scrollbar-thumb { background:var(--line); border-radius:3px; }
  .fr { flex:0 0 auto; position:relative; height:var(--th); padding:0;
        border:2px solid transparent; border-radius:6px; background:#000;
        overflow:hidden; cursor:pointer; scroll-snap-align:center; }
  /* width comes from the height and the aspect-ratio set on each tile */
  .fr img { display:block; height:100%; width:auto; }
  .fr .n { position:absolute; left:4px; bottom:3px; padding:2px 4px;
           font-size:10px; line-height:1; border-radius:4px; background:#0009;
           color:#dce7ea; pointer-events:none; }
  .fr[aria-current=true] { border-color:var(--hot); }
  .fr:focus-visible { outline:2px solid var(--hot); outline-offset:1px; }
  @media (hover: hover) and (pointer: fine) {
    .fr:hover:not([aria-current=true]) { border-color:var(--ink); }
  }
  @media (min-width:1600px) { #strip { --th:120px; } }
  @media (max-width:620px) {
    #strip { --th:60px; padding:5px 6px; gap:5px; }
    .fr { border-width:1.5px; border-radius:5px; }
    .fr .n { display:none; }          /* the number is noise at this size */
  }
  /* A phone on its side has width to spare and no height at all: a strip
     under the picture would leave it 180 px tall. So the strip goes down the
     right edge instead, one column of tiles as wide as --tw, and the picture
     keeps every row it had. The page becomes a grid for this alone; in every
     other shape it is the column of header, stage, strip that the flex above
     describes. */
  @media (max-height:500px) and (orientation:landscape) {
    body { display:grid; grid-template-rows:auto minmax(0,1fr);
           grid-template-columns:minmax(0,1fr) auto; }
    header { grid-column:1 / -1; }
    #stage { grid-row:2; grid-column:1; }
    #strip { --tw:88px; grid-row:2; grid-column:2; flex-direction:column;
             align-items:stretch; overflow-x:hidden; overflow-y:auto;
             border-top:0; border-left:1px solid var(--line); padding:6px;
             touch-action:pan-y; scroll-snap-type:y proximity; }
    .fr { height:auto; width:var(--tw); }
    .fr img { height:auto; width:100%; }
    .fr .n { display:none; }
  }

  #stage { position:relative; flex:1; overflow:hidden; touch-action:none; }
  #shot { position:absolute; inset:0; width:100%; height:100%; display:block;
          user-select:none; -webkit-user-drag:none; }
  #note { position:absolute; left:10px; bottom:8px; right:10px; color:var(--ink);
          font-size:12px; text-shadow:0 1px 3px #000; pointer-events:none; }
  /* Over the loading scrim, under the settings panel: the same z-index as the
     scrim and later in the stage than it, so a server that stops answering
     mid-load says so where it can be read rather than behind the blur. */
  #err { position:absolute; left:10px; right:10px; bottom:28px; color:var(--warn);
         font-size:12px; pointer-events:none; z-index:4; }
  /* The scene is provisional: its cameras are still being adjusted on the
     server, and the picture will be replaced. Said over the picture, where the
     eye already is, and not in the header among the controls. */
  #improving { position:absolute; top:10px; left:10px; display:flex; gap:8px;
               align-items:center; padding:6px 12px 6px 10px; border-radius:999px;
               background:#0c1417d9; border:1px solid var(--hot); color:#e8f5f7;
               font-size:12.5px; pointer-events:none; max-width:calc(100% - 20px);
               box-shadow:0 2px 10px #0008; }
  #improving[hidden] { display:none; }
  /* A scene being loaded onto the GPU takes seconds when the bundle is big,
     and until it is there the canvas still holds the last one. That picture
     is not the scene being asked for, so it goes behind a scrim that says
     which scene is coming and takes the drags that would otherwise orbit the
     old one -- both halves matter: a picture that still moves reads as the
     answer, and the renders it costs are queued behind the very load
     everybody is waiting for. Under the settings panel (z-index 5), over
     everything else in the stage. */
  #loading { position:absolute; inset:0; z-index:4; display:flex; gap:10px;
             align-items:center; justify-content:center; cursor:progress;
             background:#0c1417d0; color:#e8f5f7; font-size:13.5px;
             backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px); }
  #loading[hidden] { display:none; }
  #loading b { color:var(--hot); font-weight:600; }
  #loading i { flex:0 0 auto; width:10px; height:10px; border-radius:50%;
               background:var(--hot); animation:pulse 1.4s ease-in-out infinite; }
  #improving b { color:var(--hot); font-weight:600; }
  #improving i { flex:0 0 auto; width:9px; height:9px; border-radius:50%;
                 background:var(--hot); animation:pulse 1.4s ease-in-out infinite; }
  @keyframes pulse { 0%,100% { opacity:.25; transform:scale(.8); }
                     50% { opacity:1; transform:scale(1.15); } }

  /* The settings are a panel and not a page: they are touched once a week and
     the scene is why anyone is here. */
  #panel { position:absolute; top:0; right:0; bottom:0; width:370px;
           max-width:100%; background:var(--card); overflow-y:auto;
           border-left:1px solid var(--line); padding:14px; z-index:5; }
  #panel[hidden] { display:none; }
  #panel h2 { margin:0 0 2px; font-size:13px; letter-spacing:.08em;
              text-transform:uppercase; color:var(--ink); }
  #panel .sub { color:var(--ink); font-size:12px; margin:0 0 14px; }
  .row { display:block; margin:0 0 12px; }
  .row > span { display:flex; justify-content:space-between; gap:8px;
                align-items:baseline; }
  .row input,.row select { width:100%; margin-top:4px; }
  .row em { display:block; color:var(--ink); font-size:11.5px; font-style:normal;
            margin-top:3px; }
  .row i { color:#dfa451; font-style:normal; font-size:11px; }
  .actions { display:flex; gap:8px; margin:16px 0 10px; }
  .actions button { flex:1; }
  #saved { color:var(--hot); font-size:12px; min-height:16px; }
  #who { color:var(--ink); font-size:11.5px; border-top:1px solid var(--line);
         margin-top:12px; padding-top:10px; white-space:pre-line; }
  #urls { font-size:11.5px; margin-top:8px; line-height:1.7; }
  #urls a { color:var(--hot); text-decoration:none; }

