/* frames.css: the frames of /museum (frames.js; the frames spec, §2 the
   recipes, §5 the hang and the viewer), from labs/museum-frames, where Sha
   chose them (Oct 13, 2026). Loaded after museum.css.

   One markup draws every style, on the wall and in the viewer:
     a.tile.fr.f-<key> > span.mat > span.win > img                a room's tile
     figure.slide > span.fr.f-<key> > span.mat > span.win > img   a viewer slide
   .fr        the outer box: the face (its padding), the outer edge, the shadow on the wall
   .mat       a mat, a backing, or the reveal of wall white inside the face
   .win       the window the picture shows through: its --tint, and every ring
              drawn outside it (an edge, a bevel, the float's shadow), never on it
   frames.js writes the lengths (snapped to device pixels) on the outer box:
     --u   the unit: the room's row target, or the viewer's own (SPEC §3)
     --dp  one device pixel; --ft/--fx/--fb the face; --mt/--mx/--mb the mat's
     padding (reveal + mat + bevel); --bev the bevel; --tone the mat's tone
   Every shadow falls straight down (x 0, y > 0): one light, above, on the
   center line. Every variable that holds var(--u) is declared on .f-<key>,
   which carries --u (SPEC §5.6). Nothing here matches a page with no frame
   on it: every rule needs .fr, .f-<key>, a framed row or slide, or edit
   mode's frame line and panel (.fl, .fp). */

.fr{--face:transparent;--matc:transparent}
.tile.fr,.slide > .fr{background:var(--face);padding:var(--ft) var(--fx) var(--fb);
  box-shadow:var(--edge,0 0 #0000),var(--wallsh,0 0 #0000)}
.fr > .mat{display:block;height:100%;background:var(--matc);padding:var(--mt) var(--mx) var(--mb);box-shadow:var(--rail,0 0 #0000)}
.fr .win{display:block;position:relative;width:100%;height:100%;background:var(--tint);box-shadow:var(--ring,0 0 #0000)}
.fr .win > img{display:block;width:100%;height:100%}
/* a frame and its picture arrive as one object: the frame waits, unseen, for its picture (img.in),
   and the two fade in together, so no empty frame is ever drawn. The picture inside is always
   opaque; the frame carries the fade. A re-hang moves decoded pictures in, already .in, so a
   frame change never blanks (museum.js pool) */
@supports selector(:has(a)){
  .tile.fr,.slide > .fr{opacity:0;transition:opacity .7s var(--ease)}
  .slide > .fr{transition-duration:.45s}
  .tile.fr:has(img.in),.slide > .fr:has(img.in){opacity:1}
  .fr .win > img{opacity:1}
  /* a husk (frames.js morph) has no picture to wait for: it is drawn, and faded, by the morph alone */
  .tile.fr.husk,.slide > .fr.husk{opacity:1;transition:none}
  @media (hover:hover) and (pointer:fine){.tile.fr:hover .win > img{opacity:.9}}
  @media (prefers-reduced-motion:reduce){.tile.fr,.slide > .fr{transition:none}}
}
/* taken down (edit mode): the picture dimmed in its frame, as on a bare tile */
.tile.fr.stored .win > img.in{opacity:.38}

/* ═══ the recipes (SPEC §2) ═══ */
/* face-mount: no frame; a device pixel's edge and a short, soft shadow on the wall below */
.f-facemount{--ring:0 0 0 var(--dp) rgba(17,17,17,.10);
  --wallsh:0 calc(var(--u)*.0025) calc(var(--u)*.004) rgba(17,17,17,.08),0 calc(var(--u)*.008) calc(var(--u)*.016) rgba(17,17,17,.05)}
/* hairline: one device pixel of ink (an inset spread, never a border), a hair of wall white inside it */
.f-hairline{--face:var(--wall);--edge:inset 0 0 0 var(--dp) var(--ink)}
/* black metal: a narrow flat black face, a fine white reveal */
.f-metal{--face:#161616;--matc:var(--wall);
  --wallsh:0 calc(var(--u)*.0015) calc(var(--u)*.0025) rgba(17,17,17,.07),0 calc(var(--u)*.004) calc(var(--u)*.008) rgba(17,17,17,.04)}
/* thick black: a wide black face, unmatted, a fine white reveal */
.f-thick{--face:var(--ink);--matc:var(--wall);
  --wallsh:0 calc(var(--u)*.002) calc(var(--u)*.003) rgba(17,17,17,.07),0 calc(var(--u)*.006) calc(var(--u)*.012) rgba(17,17,17,.05)}
/* white frame: a white face on the white wall, drawn by its two edge lines and its shadow */
.f-white{--face:#fff;--edge:inset 0 0 0 var(--dp) rgba(17,17,17,.16);--ring:0 0 0 var(--dp) rgba(17,17,17,.16);
  --wallsh:0 calc(var(--u)*.002) calc(var(--u)*.003) rgba(17,17,17,.08),0 calc(var(--u)*.006) calc(var(--u)*.012) rgba(17,17,17,.06)}
/* mat: a black face around an off-white mat, weighted at the foot; the white core of its bevel at the window */
.f-mat{--face:var(--ink);--matc:var(--tone,#faf9f6);
  --rail:inset 0 calc(var(--u)*.006) calc(var(--u)*.010) calc(var(--u)*-.004) rgba(17,17,17,.10);
  --ring:0 0 0 var(--bev) #fff,0 0 0 calc(var(--bev) + var(--dp)) rgba(17,17,17,.07);
  --wallsh:0 calc(var(--u)*.002) calc(var(--u)*.003) rgba(17,17,17,.07),0 calc(var(--u)*.006) calc(var(--u)*.012) rgba(17,17,17,.05)}
/* float: the picture raised on an off-white backing in a thin black box; its shadow falls on the backing */
.f-float{--face:#161616;--matc:#f6f5f2;
  --rail:inset 0 calc(var(--u)*.010) calc(var(--u)*.016) calc(var(--u)*-.007) rgba(17,17,17,.12);
  --ring:0 calc(var(--u)*.002) calc(var(--u)*.003) rgba(17,17,17,.14),0 calc(var(--u)*.007) calc(var(--u)*.012) rgba(17,17,17,.10),0 calc(var(--u)*.014) calc(var(--u)*.024) rgba(17,17,17,.06);
  --wallsh:0 calc(var(--u)*.003) calc(var(--u)*.004) rgba(17,17,17,.07),0 calc(var(--u)*.009) calc(var(--u)*.018) rgba(17,17,17,.05)}

/* ═══ a room (SPEC §5.1, §5.3) ═══ */
/* a row that holds any frame lines up its pictures, not its frames: each tile
   is let down by its row's deepest top inset less its own (museum.js hang) */
.hrow.framed{align-items:flex-start}
/* a re-hang's husks (frames.js morph): frames without their pictures, the old ones and the one coming up,
   carried beneath the real tiles, whose pictures are never copied; a layer just before the wall, placed
   where the wall begins */
.ghosts{position:absolute;width:0;height:0;pointer-events:none}
.ghosts > .tile{position:absolute}

/* ═══ the viewer (SPEC §5.4) ═══ */
/* a framed slide holds its own husks (frames.js morph): it is their containing block, so they stay on their
   slide wherever the track is scrolled, and the stacking context they go under the picture in (z-index -1) */
.track > .slide:has(> .fr){position:relative;isolation:isolate}
/* the outer box fits the slide's content box: the picture is
   min(cw − 2x, (ch − top − bottom)·ar, its front file's width), the frame around it.
   --fx2 is 2x, --fy top + bottom, --ar, --cw and the slide's box (--bw, --bh) as museum.css gives the bare img */
.slide > .fr{display:block;flex:none;max-width:100%;
  width:calc(min(var(--bw,100cqw) - var(--fx2), (var(--bh,100cqh) - var(--fy)) * var(--ar,1), var(--cw,100cqw)) + var(--fx2))}
.slide > .fr > .mat{height:auto}
.slide > .fr .win{height:auto;aspect-ratio:var(--ar,1)}
.slide > .fr .win > img{width:100%;height:100%;max-width:none;max-height:none}
/* edit mode on a phone: the picture lifted above the risen form is carried by the morph, not by the
   slide's own ease, where it wears a frame (museum.js raiseLabel) */
#viewer.editing .slide:has(> .fr){transition:none}

/* ═══ edit mode: the frame line, the last field of a form, and its panel ═══ */
/* the line: "frame" and the frame worn, in the form's quiet type; the frame is the button that opens the panel */
.fl{margin:0;font-variant-numeric:lining-nums proportional-nums}
.form .fl{display:flex;align-items:baseline;font-size:.92rem;color:var(--faint)}
.fl .fl-w{flex:none;margin-right:.45em}
.fl .fl-v{position:relative;min-height:24px;text-align:left;color:var(--ink-2);
  text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.22em;text-decoration-color:var(--hair);transition:text-decoration-color .3s var(--ease)}
.fl .fl-v::before{content:"";position:absolute;inset:-9px -.4em}
/* The panel opens over the page, never in its flow, so nothing under it moves (Sha, Oct 12): over the
   room's own head (frames.js placeRoomPanel), over the form's fields above its line in the viewer. A
   white sheet with a hairline edge, eased in by its opacity and a short rise; its words come up only once
   the sheet has covered what lies under it, and go before it does, so two sets of words never show at
   once. Closed, it is out of the tab order */
.fp{opacity:0;visibility:hidden;transition:opacity .22s var(--ease) .14s,transform .35s var(--ease),visibility 0s linear .36s}
.fp.open{opacity:1;visibility:visible;transition:opacity .22s var(--ease),transform .35s var(--ease),visibility 0s}
.fp > .fp-in{min-height:0;opacity:0;transition:opacity .14s var(--ease)}
.fp.open > .fp-in{opacity:1;transition:opacity .26s var(--ease) .2s}
/* drawn again open (frames.js wire): at once, as it was seen */
.fp.open.now,.fp.open.now > .fp-in{transition:none}
/* a room's: over its head, above the corner's bar, which it may cover on a phone; so the room's view
   keeps no layer of its own once it has come in (its fade holds none after it ends) */
:is(.roomhead,.mapedit):has(.fp.rfp){position:relative}
:is(#room,#mapview).view:has(.fp.rfp){animation-fill-mode:backwards}
:is(.roomhead,.mapedit) .fp.rfp{position:absolute;z-index:12;left:calc(-1.1rem - 1px);top:calc(100% + .55rem);width:max-content;max-width:calc(100vw - 2 * var(--g));margin:0;
  padding:.95rem 1.1rem .7rem;background:var(--wall);border:1px solid var(--hair);transform:translateY(-6px);
  overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}
:is(.roomhead,.mapedit) .fp.rfp.open{transform:none}
/* its words from the top: the row, the mat's settings, then the circles and the rest (.fp-low) right under
   them, so the settings open between the row and the circles and carry the circles down; only the panel
   over a phone's head keeps the circles and the rest at its foot (.over, below) */
.fp-body{display:flex;flex-direction:column;font-size:.94rem;line-height:1.5}
/* a room's panel names its level: "for the gallery Big steppers", the name in italic */
.fp-h{margin:0 0 .3rem;color:var(--ink-2)}
.fp-h i{color:var(--ink)}
/* the choices: every word 24 × 24 at least, 8 px from the next row (WCAG 2.5.8); the one worn in ink */
.fp-ws{display:flex;flex-wrap:wrap;align-items:baseline;row-gap:8px}
.fp-i{display:inline-flex;align-items:baseline;white-space:nowrap}
.fp-w{position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:24px;min-width:24px;padding:0;white-space:nowrap;color:var(--faint);
  text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.22em;text-decoration-color:transparent;transition:color .3s var(--ease),text-decoration-color .3s var(--ease)}
.fp-w[aria-pressed="true"]{color:var(--ink)}
.fp-ws .dot{color:var(--faint);margin:0 .42em;user-select:none}
/* each word's target grows past its type, never moving it: across half the dot's room on each side and half
   the gap to the next row, so a thumb that lands between two words or two rows still finds one */
.fp-w::before,.fp-c::before{content:"";position:absolute;inset:-4px -.5em}
/* the mat's three settings, and the line naming the pictures with their own frame: each eased in and out
   from nothing to its height inside the panel, on the morph's clock (frames.js rowsTo) */
.fp-set,.fp-own,.fp-cw{display:grid;grid-template-rows:0fr;opacity:0;visibility:hidden}
.fp-set.on,.fp-own.on,.fp-cw.on{grid-template-rows:1fr;opacity:1;visibility:visible}
.fp-set > .fp-in,.fp-own > .fp-in,.fp-cw > .fp-in{min-height:0;overflow:hidden;padding:5px 5px 0;margin:-5px -5px 0;opacity:1;transition:none}   /* a focus ring's room above the first row too */
.fp-cw > .fp-in{padding-bottom:5px;margin-bottom:-5px}   /* and under the last circle's */
.fp-set > .fp-in{padding-bottom:4px;margin-bottom:-4px}   /* and the last setting's target its full height */
.fp-sets{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:1.6rem;row-gap:8px;margin:.45rem 0 0;font-size:.9rem}
/* a setting: its label in one column, its words wrapping in the next (a hanging indent) */
.fp-sets .fp-ws[data-k]{display:grid;grid-template-columns:max-content auto;column-gap:.55em;align-items:baseline}
.fp-sets .fp-sl{color:var(--ink-2)}
.fp-opts{display:flex;flex-wrap:wrap;align-items:baseline;row-gap:8px}
/* the circles: this frame for the gallery, for the museum. A hairline ring, filled in ink with a white tick
   while that level wears the frame. A circle a press could not change (the frame none, with nothing to go
   back to) is drawn faint and unticked, and a press does nothing (frames.js circleState); it is always
   there, so nothing moves as it comes alive */
.fp-cs{display:flex;flex-direction:column;align-items:flex-start;row-gap:8px;margin:.75rem 0 0}
.fp-c,.form label.fp-c{position:relative;display:inline-flex;align-items:center;gap:.6em;min-height:24px;margin:0;font-size:inherit;color:var(--ink-2);cursor:pointer;
  transition:color .3s var(--ease),opacity .3s var(--ease),visibility 0s}
.fp-c:has(input:checked){color:var(--ink)}
.fp-cs .fp-c.lock{opacity:.4;cursor:default}
.fp-c input{appearance:none;-webkit-appearance:none;flex:none;position:relative;top:var(--cy,-.04em);width:.84em;height:.84em;margin:0;border:0;border-radius:50%;cursor:inherit;
  background:transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.4 8.4 6.9 10.8 11.6 5.6' fill='none' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/100% 100% no-repeat;
  box-shadow:inset 0 0 0 1px currentColor;transition:background-color .3s var(--ease),box-shadow .3s var(--ease)}
.fp-c input:checked{background-color:currentColor}
/* the room panel's last row: "2 pictures have their own frame" (eased in and out by its height and opacity,
   frames.js rowsTo; shut, it gives back its width) */
.fp-end{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;column-gap:1rem;margin:.5rem 0 0}
/* a room panel's circle and that line share a row where they fit, the line at its right (the review, Oct 13: a
   line saved, where a level of the map's panel stands beside its count) */
.fp.rfp .fp-low{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;column-gap:1.4rem;row-gap:.5rem}
.fp.rfp .fp-low > .fp-end{margin:0}
.fp.rfp .fp-low > .fp-end:only-child{margin-top:.5rem}
.fp-end > .fp-own{flex:0 1 auto}
.fp-end > .fp-own.gone{width:0;flex-basis:0}
/* the line's words step to the next of those pictures, underlined as the frame line is */
.fp-ol{margin:0;font-size:.9rem}
.fp-ol .fp-w{color:var(--ink-2);white-space:normal;text-align:left;text-decoration-color:var(--hair)}
/* over the head, from the top of the screen to its line: the circles and the rest at its foot */
:is(.roomhead,.mapedit) .fp.rfp.over{display:flex;flex-direction:column}
:is(.roomhead,.mapedit) .fp.rfp.over .fp-low{margin-top:auto}
:is(.roomhead,.mapedit) .fp.rfp.over > .fp-in{flex:1 0 auto;display:flex;flex-direction:column}
:is(.roomhead,.mapedit) .fp.rfp.over .fp-body{flex:1 0 auto}
/* a phone's room panel (frames.js dockPanel): docked at the foot of the screen, just above the edit bar (the
   bar's own offset from the foot, its height --ebh, and 8 px), the room's width less its gutters, as tall as
   its words, the pictures scrolling beneath it; past its most it scrolls inside itself */
:is(.roomhead,.mapedit) .fp.rfp.dock{position:fixed;top:auto;left:max(var(--g),env(safe-area-inset-left));right:max(var(--g),env(safe-area-inset-right));
  bottom:calc(env(safe-area-inset-bottom) + 14px + var(--ebh,0px) + 8px);width:auto;max-width:none;transform:none}
/* its form's Save and what it says, at its foot as the form has them (frames.js DOCKACT): only in the dock */
.fp-act{display:none}
:is(.roomhead,.mapedit) .fp.rfp.dock .fp-act{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;margin-top:.6rem}
.fp-act .fp-sv{position:relative;flex:none;min-height:24px;padding:.1rem 0;font-size:.94rem;color:var(--ink);border-bottom:1px solid var(--ink)}
.fp-act .fp-sv::before{content:"";position:absolute;inset:-6px -.5em}
.fp-act .fp-st{min-width:0;font-size:.84rem;color:var(--faint);font-style:italic;text-align:right;font-feature-settings:"ss04" 1,"ss09" 1,"lnum" 1}
.fp.tt .fp-act{margin-top:var(--rg,6px)}
/* a panel that comes and goes as one movement (frames.js ride: the dock, and a picture's on a phone): its sheet
   and its words together, moved by its own animations alone */
.fp.ride,.fp.ride.open,.fp.ride > .fp-in,.fp.ride.open > .fp-in{transition:none}
.fp.ride > .fp-in{opacity:1}
#label .fp.vfp.ride{transform:none}
/* a wide window's room head: what the Save says stands beside it, not at the form's far end, so a panel beside
   the head keeps its room clear and never lies over it (frames.js statusRight; the review, Oct 13: on a map
   level a mat's settings reached down over "Unsaved changes") */
@media (min-width:900px){.roomedit .form:has(> .ffr) > .actions{justify-content:flex-start}}
/* a dot before a line's break is not drawn (frames.js eol): a wrapped row of words never ends in one */
.dot.eol{visibility:hidden}

/* a picture's panel, in the viewer's form: a white box over the form's fields, standing on its line, its
   hairline hugging its words. The mat's settings open above the row, so the box grows upward from its
   line and the row, the word just pressed in it and the circles under it stay where they are. Past its
   most (frames.js placeFormPanel) it scrolls inside itself; column-reverse keeps it on its line and lets
   the part over the top scroll into view */
#label .ffr{position:relative}
#label .fp.vfp{position:absolute;z-index:2;left:-10px;right:-10px;bottom:calc(100% + .45rem);display:flex;flex-direction:column-reverse;
  transform:translateY(6px);overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}
#label .fp.vfp.open{transform:none}
#label .fp.vfp > .fp-in{flex:none;padding:.85rem 10px .25rem;padding-top:calc(.85rem + var(--lift,0px));border:1px solid var(--hair);background:var(--wall)}   /* --lift: frames.js lift */
#label .fp.vfp .fp-body{font-size:.86rem}
#label .fp.vfp .fp-sets{font-size:.86rem}
#label .fp.vfp .fp-body > .fp-set{order:-1}
#label .fp.vfp .fp-set .fp-sets{margin:0 0 .75rem}   /* set apart from the row as the circles are */
#label .fp.vfp .fp-cs{margin-bottom:.45rem}

/* a phone's room: the panel the head's width and a little more, its words aligned with the head's */
@media (max-width:640px){
  :is(.roomhead,.mapedit) .fp.rfp{left:-10px;right:-10px;width:auto;max-width:none;padding:.55rem 9px .4rem}
  :is(.roomhead,.mapedit) .fp.rfp .fp-body,:is(.roomhead,.mapedit) .fp.rfp .fp-sets,:is(.roomhead,.mapedit) .fp.rfp .fp-ol{font-size:.86rem}
}
/* a phone's panels: closer rows (6 px between, every word still 24 px tall) */
@media (max-width:899.98px){
  :is(.roomhead,.mapedit) .fp.rfp .fp-h{margin-bottom:.15rem}
  .fp .fp-ws,.fp .fp-opts,.fp .fp-sets,.fp .fp-end{row-gap:6px}
  .fp .fp-w::before,.fp .fp-c::before{inset:-3px -.5em}
  .fp .fp-sets{margin-top:.35rem}
  .fp .fp-cs{margin-top:.55rem;row-gap:6px}
  :is(.roomhead,.mapedit) .fp.rfp .fp-low{row-gap:.35rem}
  :is(.roomhead,.mapedit) .fp.rfp .fp-low > .fp-end:only-child{margin-top:.35rem}
  #label .fp.vfp > .fp-in{padding:.6rem 9px .2rem;padding-top:calc(.6rem + var(--lift,0px))}
  #label .fp.vfp .fp-set .fp-sets{margin:0 0 .55rem}
}
/* a touch screen's phone panels (frames.js fitGap): the rows --rg apart, the most the panel's room gives,
   and each word's and circle's target half of that above and below it, so a thumb finds one wherever it
   lands and neighbors never overlap. A wide screen, and a mouse, keep the spacing above */
.fp.tt .fp-ws,.fp.tt .fp-opts,.fp.tt .fp-sets,.fp.tt .fp-end,.fp.tt .fp-cs{row-gap:var(--rg,6px)}
.fp.tt .fp-sets,.fp.tt .fp-cs,.fp.tt .fp-end{margin-top:var(--rg,6px)}
.fp.rfp.tt .fp-low{row-gap:var(--rg,6px)}
.fp.rfp.tt .fp-low > .fp-end{margin-top:0}
.fp.rfp.tt .fp-low > .fp-end:only-child{margin-top:var(--rg,6px)}
#label .fp.vfp.tt .fp-set .fp-sets{margin:0 0 var(--rg,6px)}
.fp.tt .fp-w::before,.fp.tt .fp-c::before{inset:calc(var(--rg,6px) / -2) -.5em}
.fp.tt .fp-h{margin-bottom:calc(var(--rg,6px) / 2)}
:is(.roomhead,.mapedit) .fp.rfp.tt{padding-bottom:calc(var(--rg,6px) / 2 + 2px)}
/* in a phone's form, two circles side by side where they fit */
#label .fp.vfp.tt .fp-cs{flex-direction:row;flex-wrap:wrap;column-gap:1.4rem;margin-bottom:calc(var(--rg,6px) / 2)}
@media (hover:hover) and (pointer:fine){
  .fl .fl-v:hover,.fp-w:hover{text-decoration-color:currentColor}
  .fp-w:hover,.fp-c:not(.lock):hover{color:var(--ink)}
}
@media (prefers-reduced-motion:reduce){.fp,.fp.open,.fp > .fp-in,.fp.open > .fp-in,.fp-w,.fp-c,.fp-c input,.fl .fl-v{transition:none}}
