/* ==========================================================================
   Three-dimensional structure figures.

   The furniture is the report's own: `.figure`, `.figure__frame`,
   `.figure__legend`, `.controls` and `.seg` all come from components.css and
   are not restated here. What this file adds is the stage the viewer draws
   into, the descriptions that stand for the structures whether or not a scene
   is ever drawn, and the reset button, which is the one control the filter
   bars do not already have.

   Two states, and the difference between them is one attribute the script sets
   once it has a working viewer. Without it the figure is a short reference
   list: every structure the figure could show, described, with its resolution
   and a link to its databank entry, and no empty box above them. With it the
   list narrows to whichever structure is on the stage. No prose is hidden by
   the `hidden` attribute here, because prose hidden that way is prose a reader
   without a script would lose, and the only thing they are meant to lose is
   the rotation. The two elements that do ship hidden — the color key and the
   line explaining a scene that failed — carry no fact of their own and mean
   nothing without a scene, and both are given an explicit `[hidden]` rule
   below, because a class from components.css otherwise outranks the user
   agent's and leaves them on screen.

   The stage is white. A structure viewer's default is black, and black is the
   color of a game, not of a page that means to look like a figure in a
   journal and to survive being printed.
   ========================================================================== */

.structure { margin: var(--s-7) 0; }

.structure__heading {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  color: var(--ink-strong);
  margin: 0 0 var(--s-2);
}

.structure__frame { padding: var(--s-5); }

/* --- Controls -----------------------------------------------------------
   Built by the script and only ever present when there is a viewer under
   them, so there is no disabled state to write here: a figure that cannot be
   drawn has no controls at all rather than dead ones. `.controls` brings the
   row and the hairline under it. */
.structure__controls { align-items: baseline; margin-bottom: var(--s-4); }

/* Both buttons sit at the end of the row, in the order a reader needs them:
   take control first, undo what you did to it second. The push to the end lives
   on the wrapper rather than on either button, because a `margin-left: auto` on
   each of two adjacent buttons pushes them apart and leaves the gap in the
   middle of the pair rather than before it. */
.structure__actions {
  display: flex;
  gap: var(--s-2);
  margin-left: auto;
  align-self: center;
}

.structure__reset,
.structure__hold {
  font: inherit;
  font-size: var(--step--1);
  padding: 0.24rem 0.75rem;
  border: 1px solid var(--rule);
  /* Not a capsule. The filters elsewhere on the page stopped being pills
     because a rounded tinted capsule is the badge shape and reads as a label
     the document is applying rather than a control the reader is holding. A
     reset is a different kind of control — an action, not a state — so it
     keeps its outline where the filters lost theirs, and it takes the same 5px
     the search field takes, because those two are the page's only bordered
     controls and there is no reason for them to disagree about a corner. */
  border-radius: 5px;
  background: var(--card);
  color: var(--ink-3);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out);
}
.structure__reset:hover,
.structure__hold:hover { color: var(--ink); border-color: var(--connector); }
.structure__reset:active,
.structure__hold:active { color: var(--accent-ink); border-color: var(--accent); }

/* The hold button is a toggle, so unlike the reset beside it there is a state
   to draw. It borrows the accent the reset shows only while a finger is down,
   which is the page's existing vocabulary for a control that is doing
   something, and it holds it for as long as the reader has the scene. */
.structure__hold[aria-pressed="true"] {
  color: var(--accent-ink);
  border-color: var(--accent);
}

/* One focus treatment for every part of the figure that takes focus, drawn in
   the azure focus color rather than the brand green, so the browser's
   position marker can never be mistaken for a pressed state. */
.structure__controls button:focus-visible,
.structure__canvas:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The canvas ring alone is drawn inward. The stage clips its children at its
   own edge, so a ring offset outward from an element that fills the stage is a
   ring the reader never sees. Inward matches what the shield's own ring does. */
.structure__canvas:focus-visible {
  outline-offset: -3px;
}

/* --- The shield ----------------------------------------------------------
   A transparent button covering the stage, and the reason it exists is in
   `structure.js` at length: the viewer library binds six non-passive pointer
   listeners to its own canvas in its constructor, reads its `nomouse` setting
   exactly once while doing so, and binds each listener through a temporary that
   cannot afterwards be handed back to `removeEventListener`. There is therefore
   no way to ask the library to stop listening. What there is a way to do is to
   make sure the events never reach it, and an element lying over the canvas
   does that with no `preventDefault` anywhere and so no argument with the
   page's own scrolling. A reader who scrolls past the figure scrolls past it;
   the wheel is the browser's until they say otherwise.

   It is a button rather than a bare div because it is a control: it has one
   action, it belongs in the tab order beside the two in the row above, and a
   reader on a keyboard reaches it and presses it in the ordinary way. The
   accessible name says what pressing it does and what holds until they do.

   The focus ring turns inward. Every other ring on the page sits outside the
   thing it marks, but this one is flush with the stage's own border, and a ring
   drawn outside would land on the page around the figure rather than on the
   figure. */
.structure__shield {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--s-3);
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.structure__shield:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -3px;
  border-radius: 6px;
}
.structure__shield[hidden] { display: none; }

/* The label the shield shows when a pointer crosses it. Held back until then,
   because a figure with a permanent caption floating over its middle is a
   screenshot nobody wants and a print nobody can use, and the sentence under
   the control row has already said the same thing to everyone who never brings
   a pointer near it at all.

   Set as the two buttons above are set — white card, hairline, the same 5px —
   so that it reads as one more control of the page rather than as an overlay
   the viewer has put there. It is at the foot of the stage rather than across
   the center of it: the protein is the reason the figure exists and a label
   over the middle of it hides the thing it is inviting the reader to look at. */
.structure__shield-chip {
  font-size: var(--step--2);
  line-height: 1;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 5px;
  background: var(--card);
  color: var(--ink-3);
  opacity: 0;
  transform: translateY(var(--s-1));
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
.structure__shield:hover .structure__shield-chip,
.structure__shield:focus-visible .structure__shield-chip {
  opacity: 1;
  transform: none;
  color: var(--ink);
  border-color: var(--connector);
}

/* While the reader has the scene the stage says so, in the one way a figure can
   say it without becoming a card: the hairline it already had, a shade darker.
   No fill, no tint, no glow. */
.structure[data-structure-held] .structure__stage { border-color: var(--connector); }

/* The hint sits on its own line under the row. It is written by the script and
   describes gestures, so it exists only where those gestures do. */
.structure__hint {
  flex-basis: 100%;
  margin: var(--s-2) 0 0;
  font-size: var(--step--2);
  line-height: var(--lh-body);
  color: var(--ink-4);
  transition: color var(--dur-2) var(--ease-out);
}

/* --- The invitation ------------------------------------------------------
   The scene turns once on its own, and `structure.js` carries the argument for
   that. What is here is the part of the invitation that survives the turn: a
   reader who has asked for reduced motion and is offered none of it, a reader
   who arrives at the figure after the single revolution has run out, and a
   reader who reaches it from the keyboard and never sees a pointer at all.

   The cursor is the older half and the cheaper, and it is now split across the
   two states the figure has. Over the shield, before the reader has asked for
   anything, it is `pointer`: the honest cursor for an element whose whole
   behavior is that pressing it does one thing. Once they have pressed it the
   shield goes and the canvas beneath is exposed, and there the cursor is `grab`,
   and `grabbing` while the scene is being turned, which is the convention every
   map on the web has spent fifteen years teaching. Reading the two in sequence
   is the point: the figure says it can be taken hold of, and then says it is
   being held. Neither needs a guard against sitting over a stage that cannot be
   turned, because the stage is out of the flow until the script marks the figure
   live and goes back out of it if the scene later fails.

   The sentence is the other half. It was already written, already correct and
   already in the right place; what it was not was legible as an instruction.
   Set in the faintest ink on the ramp and parked under the control row, it read
   as a caption — a description of the figure rather than something to do to it.
   It is set one step up the ramp for as long as any gesture it names will
   work — the rule below spells that out state by state — and it drops back only
   when the pointer and the keyboard have both gone back to the page, which is
   when it describes something already found. One step, not two: at `--ink-2` it competes
   with the entry's own description below the stage, which is the prose that
   carries the science.

   Considered and left out: a hairline that darkens as the pointer crosses the
   frame. It says what the cursor says, on the same event, a fraction later, and
   a figure whose border answers the pointer reads as a card that can be opened
   rather than as a scene that can be turned. One signal per fact.

   Nothing here needs a reduced-motion rule of its own. `tokens.css` zeroes every
   duration under it, so the one transition above becomes a change of color with
   no time in it — which is what a reader who asked for stillness should get from
   a cue that was never motion in the first place. */
.structure__canvas { cursor: grab; }
.structure__canvas:active { cursor: grabbing; }

/* One step up the ramp while the sentence is worth reading, and that is three
   states rather than one. Before the reader takes hold it is an invitation, and
   the mark for that is `data-structure-invites`. The instant they take hold the
   script drops that mark — the invitation has been answered — and without the
   second selector here the line would fall to the faintest ink on the ramp at
   precisely the moment it stops describing the figure and starts listing what
   the reader can now do to it. The third mark, `data-structure-keys`, covers
   the reader who tabbed onto the canvas without taking the pointer: the arrow
   keys are live for them, and the sentence that says so should not be the
   faintest line on the page while they are. */
.structure[data-structure-invites] .structure__hint,
.structure[data-structure-held] .structure__hint,
.structure[data-structure-keys] .structure__hint { color: var(--ink-3); }

/* --- Stage ---------------------------------------------------------------
   Absent until the script says otherwise. A box reserved for a scene that
   never arrives is the failure this whole arrangement is built to avoid, so
   the space is taken at the moment a viewer is known to be openable and not
   one paint earlier. */
.structure__stage { display: none; }

.structure[data-structure-live] .structure__stage {
  display: block;
  position: relative;
  /* Held to a shape rather than to a height, so the scene is the same
     composition on a phone as on a desk. The cap is on the width: three by two
     at 43.125rem is 460px tall at the base size, which is a figure on the page rather than a screen
     of its own. */
  aspect-ratio: var(--structure-aspect, 3 / 2);
  width: 100%;
  max-width: 43.125rem;
  margin-inline: auto;
  overflow: hidden;
  border-radius: 6px;
  /* Not quite white: a hair of tint, so the pale end of the protein and the
     page behind it stay distinguishable at the rim of the scene. */
  background: #FCFDFD;
  border: 1px solid var(--rule);
}

.structure__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* The viewer positions its own canvas absolutely inside the mount point; this
   keeps it off the rounded corner while it settles. */
.structure__canvas canvas { display: block; border-radius: 6px; }

/* While the library and the coordinates are in flight the stage is empty, and
   an empty white box with a border reads as something broken. A quiet wash
   says it is on its way, and it is a wash of the page's own sunk paper rather
   than a spinner, because a spinner over a figure that takes 200ms is more
   movement than information. */
.structure[data-structure-live] .structure__stage[data-structure-state="loading"] {
  background: var(--paper-sunk);
}

/* --- The line that appears when the scene could not be drawn -------------
   Only ever seen after a viewer opened and then failed. A hairline and a
   sentence: no tinted panel, because a failure the reader can do nothing about
   should not be the loudest thing on the page. */
.structure__why {
  margin: 0 0 var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--rule);
  font-size: var(--step--1);
  line-height: var(--lh-body);
  color: var(--ink-3);
  max-width: 68ch;
}

/* --- Legend --------------------------------------------------------------
   A key to the colors in the scene, so it appears with the scene and goes
   with it. */
.structure__legend { margin-top: var(--s-4); }

/* Two elements in this figure ship with the `hidden` attribute and are
   unhidden by the script: the legend, when a scene has been drawn, and the
   line saying why there is none, when one was attempted and failed. Both
   inherit a `display` from a class in components.css, and a class beating the
   user agent's `[hidden] { display: none }` is how a hidden element stays on
   screen. The legend did: on a machine with no graphics context the page
   showed a key to four colors in a scene that was never drawn, which is the
   one thing this figure is built not to do. So the attribute is honored here,
   at a specificity the class rules cannot outrank. It is deliberately written
   against the attribute rather than against a state class, because `hidden` is
   what the markup ships and what the script toggles. */
.structure__legend[hidden],
.structure__why[hidden] { display: none; }

/* The same trap one level down. `.figure__legend span` sets `inline-flex` on
   every item, so a key item the script puts away for a structure that has no
   such color on the stage would stay on screen without this. */
.structure__legend span[hidden] { display: none; }
/* Atoms are round. A square swatch beside a stick model reads as a chart key
   borrowed from the wrong figure. */
.structure__legend .swatch { width: 0.6875rem; height: 0.6875rem; border-radius: 50%; }

/* --- The structures, in words -------------------------------------------
   Without a viewer this is the figure: every structure it could have shown,
   in the order the switch would have offered them, separated by the page's
   hairline. With a viewer it is one description at a time, under the scene. */
.structure__entries { margin-top: var(--s-4); }

.structure__entry + .structure__entry {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}

.structure__entry-title {
  margin: 0 0 var(--s-1);
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: var(--lh-snug);
  color: var(--ink-strong);
}

.structure__entry-meta {
  margin: 0 0 var(--s-2);
  font-size: var(--step--2);
  line-height: var(--lh-body);
  color: var(--ink-4);
}
.structure__entry-link { color: var(--ink-3); }
.structure__entry-link:hover { color: var(--link); }

.structure__entry-body {
  margin: 0;
  font-size: var(--step--1);
  line-height: var(--lh-body);
  color: var(--ink-2);
  max-width: 68ch;
}

/* One at a time once the switch exists. Keyed off an attribute rather than the
   `hidden` one: `hidden` is the page's promise that a reader without a script
   still sees the words, and these are put away precisely because a script is
   running. */
.structure[data-structure-live] .structure__entry[data-structure-off] { display: none; }
.structure[data-structure-live] .structure__entry + .structure__entry {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
/* With the scene above it, the description is one entry rather than a list, so
   it takes the hairline that separates it from the picture instead of the ones
   that separated it from its neighbors. */
.structure[data-structure-live] .structure__entries {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}

/* --- Print ---------------------------------------------------------------
   A canvas prints as a bitmap at whatever the screen gave it, which at this
   size is a soft gray smear. The scene is dropped and every structure's own
   description is printed instead, which is the same trade the page makes for a
   reader whose browser cannot draw one. */
@media print {
  .structure__controls,
  .structure__stage,
  .structure__legend { display: none !important; }
  .structure[data-structure-live] .structure__entry[data-structure-off] { display: block; }
  .structure[data-structure-live] .structure__entries {
    margin-top: var(--s-4);
    padding-top: 0;
    border-top: 0;
  }
  .structure[data-structure-live] .structure__entry + .structure__entry {
    margin-top: var(--s-4);
    padding-top: var(--s-4);
    border-top: 1px solid var(--rule);
  }
}
