/* Contract: contracts/web/rules.md
 * a shared folder, the guest page, and the phone
 *
 * Part of what was style.css — 1700 lines in one file. The parts load in a fixed
 * order and MUST stay in it: the cascade is decided by source order and
 * specificity exactly as it was, because this is a split and not a rewrite.
 *
 * There are deliberately no @layer rules. Layering is the tidier shape and it
 * would have changed behaviour — a later layer beats an earlier one regardless
 * of specificity, and this stylesheet has at least one pair, `body.app > .bar`
 * written above `.bar`, where the MORE specific rule comes first and wins on
 * specificity. Putting those two in different layers silently reverses them.
 * Splitting the file is worth doing on its own; changing the cascade is a
 * separate decision that wants somebody looking at the screen.
 */
/* ---------- a folder that somebody outside the studio can see ---------- */

/* Small, quiet, and always present rather than only on hover. The one fact
   about a folder that has consequences beyond this screen should not require
   a gesture to discover — but it also must not crowd out the folder's name,
   which is why it is a 14px mark and not the word "shared". */
.fshared { flex: none; width: 14px; height: 14px; opacity: 1; color: var(--warn); }
.frow.on .fshared { color: #fff; }
.folderbar .ghost.on { color: var(--warn); border-color: var(--warn); }

/* The Share dialog. Wider than the others because a URL that wraps is a URL
   somebody copies half of. */
.sharemake { border-top: 1px solid var(--line); margin-top: 16px; padding-top: 16px; }
.sharetoggle {
  display: flex !important; align-items: center; gap: 8px;
  font-size: 13px; color: var(--ink); margin-bottom: 8px; cursor: pointer;
}
.sharetoggle input { width: auto !important; margin: 0 !important; cursor: pointer; }

#shareout {
  margin-top: 16px; padding: 14px; border-radius: 10px;
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--ok) 35%, transparent);
}
.sharerow-link { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.sharerow-link input {
  flex: 1; min-width: 0; margin: 0 !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
}
.sharerow-link .btn { flex: none; }

.sharelist { margin-top: 6px; }
.sharelist-title { margin: 20px 0 8px; font-size: 12px; color: var(--ink-dim); font-weight: 600; }
.sharerow {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 10px;
}
/* A stopped link stays listed rather than disappearing: "did I actually revoke
   Manolo's link?" is a question worth being able to answer later. */
.sharerow.dead { opacity: .55; }
.sharerow-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.sharerow-head .note { font-size: 11.5px; }
.sharerow-perms { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.sharerow-perms .sharetoggle { margin: 0; font-size: 12.5px; }
.sharerow-perms .danger { margin-left: auto; background: transparent; color: var(--accent); }
.btn.sec.tiny { padding: 4px 10px; font-size: 11.5px; }

/* ---------- the guest page (/s/<token>) ---------- */
/* Deliberately a different shape from the archive browser: no sidebar, no tabs,
   no filters. Somebody who has this link should not be able to tell how much
   more there is, and should not have to learn an interface to take one file. */

.sharepage .bar { gap: 16px; }
.sharewho { display: flex; flex-direction: column; gap: 1px; margin-right: auto; min-width: 0; }
.sharewho strong { font-size: 15px; }
.sharewho .note { margin: 0; font-size: 12px; }

.sharemain { padding: 22px 20px 60px; max-width: 1400px; margin: 0 auto; }
.sharegate { padding: 40px 0; text-align: center; }
.sharegate h2 { font-size: 17px; margin: 0 0 8px; }
.sharenote { margin: 24px 0; }

.sharemsg {
  margin: 0 0 18px; padding: 12px 14px; border-radius: 10px;
  background: var(--panel-2); font-size: 13.5px;
}
.sharemsg.ok  { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ink); }
.sharemsg.bad { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--ink); }

.sharegrid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.sharecard {
  margin: 0; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column;
}
.sharecard img {
  width: 100%; aspect-ratio: 1; object-fit: contain; background: var(--panel-2); display: block;
}
/* Transparent artwork on a flat panel reads as a white background, which is a
   lie about the file somebody is about to download. */
.sharecard.alpha img {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d9d9de 25%, transparent 25%, transparent 75%, #d9d9de 75%),
    linear-gradient(45deg, #d9d9de 25%, transparent 25%, transparent 75%, #d9d9de 75%);
  background-size: 16px 16px; background-position: 0 0, 8px 8px;
}
.sharenothumb {
  aspect-ratio: 1; display: grid; place-items: center;
  background: var(--panel-2); color: var(--ink-dim);
  font: 600 15px/1 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .06em;
}
.sharecard figcaption {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 10px 12px 12px;
}
.sharename { font-size: 12.5px; font-weight: 560; overflow-wrap: anywhere; }
.sharemeta { font-size: 11.5px; color: var(--ink-dim); }
.sharecard .btn { font-size: 12px; padding: 5px 12px; }

.sharedrop {
  margin-top: 32px; padding: 20px; border-radius: var(--radius);
  border: 1.5px dashed var(--line); background: var(--panel);
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
}
.sharedrop strong { font-size: 14px; }
.sharedrop .note { font-size: 12.5px; }

.shareuploads {
  margin-top: 20px; padding: 14px; border-radius: var(--radius);
  background: var(--panel); border: 1px solid var(--line);
}

@media (max-width: 720px) {
  /* On a phone the header actions matter more than the branding — this page is
     opened on phones far more often than the archive itself is. */
  .sharepage .bar { height: auto; padding: 10px 14px; flex-wrap: wrap; }
  .sharepage .brand h1 { display: none; }
  .sharegrid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
}

/* ---------- the phone's way in ----------
   A drop target is not an affordance a finger has. Everything above assumes a
   mouse and a window full of files to drag out of it; on a phone there is
   neither, and the only route into the archive was an "Add files…" item two
   taps deep in a menu behind somebody's name.

   Gated on the pointer rather than the width, because that is the actual
   condition: a browser window dragged narrow still has a mouse, the whole
   window is still a drop target, and it does not need a camera button. */
.addhere { display: none; }
.sharedrop-actions { display: flex; flex-wrap: wrap; gap: 8px; }

@media (pointer: coarse) {
  .addhere {
    display: block; position: fixed; z-index: 56;
    right: 18px; bottom: calc(18px + var(--floor, 0px) + env(safe-area-inset-bottom));
  }
  /* 58px because it is a thumb target, not a toolbar icon: below about 48 the
     miss rate on a phone held one-handed stops being theoretical. */
  .addfab {
    display: grid; place-items: center; width: 58px; height: 58px;
    border: 0; border-radius: 50%; cursor: pointer;
    background: var(--accent); color: #fff;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .34);
    transition: transform .12s ease;
    /* Otherwise a tap paints a grey *square* over the round button — the one
       control on this page a finger is guaranteed to land on. */
    -webkit-tap-highlight-color: transparent;
  }
  .addfab:active { transform: scale(.94); }
  .addfab svg { width: 26px; height: 26px; }

  /* Wider and taller than the account menu, which is a mouse's menu. Three
     choices under a thumb, each on one line — a label that wraps reads as two
     choices rather than one. */
  .addmenu { min-width: min(268px, calc(100vw - 36px)); }
  .addmenu .menuitem { padding: 11px 12px; font-size: 14px; }
  .addmenu .menuname { font-size: 12.5px; }

  /* The upload tray lives in this corner too and moves up out of the way. It
     arrives unannounced, so it cannot be allowed to land on top of the one
     control that is supposed to always be there. (There were two things here.
     The other was the selection bar, which no longer exists.) */
  .uploads { bottom: calc(94px + var(--floor, 0px) + env(safe-area-inset-bottom)); }
  /* And the last row of pictures stays reachable underneath it. */
  .grid { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
}

/* ---------- the phone's chrome ----------
   Four bars of furniture above a strip of pictures is the wrong way round: the
   archive is the pictures. Below this width the starred projects, the folder
   strip and the type chips are replaced by three dropdowns in the filter line, and
   the header keeps only what a phone has room for. See web/compact.js — the
   controls hidden here are still the real ones, still driven, still the single
   answer to what is selected. */
.compactsel, .uham, .narrowtext, .onlynarrow { display: none; }

/* The dot on the account button: something folded into the menu wants looking
   at. Amber rather than red — a queue draining is not a failure.
   Only where things are folded away. At full width the review chip and the
   queue chip sit in the bar saying it themselves, and a dot repeating them is
   noise on the one control that is always on screen. */
.userdot {
  display: none;
  position: absolute; top: 3px; right: 3px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--warn);
  border: 1.5px solid var(--panel);
}

/* Short counts as narrow. A phone turned sideways is 844px wide and 390px tall:
   wide enough to escape a width breakpoint and lay the full chrome out in one
   comfortable row of each — which is 217px of furniture over 173px of pictures.
   The problem was never the width. */
@media (max-width: 720px), (max-height: 520px) {
  .widetext { display: none; }
  .narrowtext { display: inline; }
  .menuitem.onlynarrow, .menu hr.onlynarrow { display: block; }

  /* --- header: one line --- */
  /* It wrapped to two because the search box demanded a full row of its own.
     The wordmark is what gives way: the mark beside it says the same thing in
     18px, and nobody on a phone is wondering which site they are on. */
  .bar { flex-wrap: nowrap; height: auto; padding: 8px 12px; gap: 10px; }
  .bar .brand h1 { display: none; }
  .searchwrap { order: 0; width: auto; flex: 1 1 auto; min-width: 0; }
  /* The right padding is a gutter reserved for #count, and it was 40px against
     a number that wants 78 — so the count was painted over the placeholder and
     the two read as one string. 76px fits the phone's shortened count (the
     total alone; see paintCount in web/grid.js) with room for a five-figure
     archive, and what is left is sized for the short prompt in web/header.js. */
  #q { padding: 8px 72px 8px 12px; font-size: 15px; }   /* 15px: iOS zooms into anything smaller */
  .headtools { gap: 4px; }
  /* Folded into the account menu, where there is room for their names. */
  .headtools > #syncbtn, .headtools > #reviewbtn, .headtools > #inboxchip,
  .headtools > #ideabtn { display: none; }
  .uname { display: none; }
  .uham { display: block; width: 22px; height: 22px; }
  .iconbtn.named { max-width: none; padding: 0 8px; position: relative; }
  .userdot { display: block; }
  /* A grey rectangle flashed over a rounded button on every tap. */
  .iconbtn, .compactsel, .chip { -webkit-tap-highlight-color: transparent; }

  /* --- the bar that goes away entirely --- */
  /* (The project tabs were the other one; they are rows in this sidebar now.) */
  body.app > .sidebar, .workspace > .sidebar { display: none; }

  /* The workspace was two rows because the folder strip took the first one.
     With the strip gone the results column would land in that `auto` row and
     size itself to its own content — which is a grid of every asset, so it
     would grow past the window instead of scrolling inside it. One row now.

     Second line is a bug this uncovered rather than caused: `body.reviewing`
     out-specifies the narrow-screen rule below 900px, so checking on a phone
     reserved a 380px column for an inspector that is a fixed overlay at this
     width and never occupied it — the grid was squeezed into the strip left
     over, against an empty half-screen. */
  .workspace, body.reviewing .workspace { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }

  /* --- filters: one scrolling line --- */
  /* Scrolls rather than wraps. Wrapping is how this became three rows deep on a
     narrow screen in the first place, and a line that scrolls costs a fixed
     44px however many controls end up in it. */
  .filters {
    flex-wrap: nowrap; overflow-x: auto; gap: 6px;
    padding: 7px 12px; scrollbar-width: none;
  }
  .filters::-webkit-scrollbar { display: none; }
  .filters > *, .filterrest > * { flex: none; }
  .filterrest { margin-left: 0; gap: 6px; flex-wrap: nowrap; }
  .chipgroup { display: none; }                 /* replaced by #kindsel */
  .compactsel { display: block; }
  select, .selectish { height: 34px; font-size: 13px; padding: 0 28px 0 10px; background-position: right 9px center; }
  /* Wide enough for a real folder name, narrow enough that the third control is
     visibly cut by the right edge — which is the only thing telling anyone the
     line scrolls. A select clips rather than ellipsises, so this is a trade
     against long names, and names are recoverable by opening the dropdown. */
  .compactsel { max-width: 37vw; }
  .check { height: 34px; font-size: 12.5px; white-space: nowrap; }
  .ghost { padding: 6px 10px; font-size: 12.5px; }

  /* --- the folder bar, when standing inside one --- */
  .folderbar { padding: 8px 12px; gap: 8px; }
  .folderbar-actions { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .folderbar-actions > * { flex: none; }

  /* --- the grid gets what is left --- */
  .grid { gap: 10px; padding: 10px 12px 96px; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
}

/* ---------- the picture, full screen ----------
   A <dialog>, so the top layer, Escape, the focus trap and the backdrop are the
   platform's job rather than ours. Everything here is about getting out of the
   way of the artwork: no panel, no border, no rounded corners around a picture
   that is the only thing anybody opened this to see. */
.lightbox {
  width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0; background: transparent; overflow: hidden;
  display: grid; grid-template-rows: 1fr auto; place-items: center;
}
.lightbox:not([open]) { display: none; }
.lightbox::backdrop { background: rgba(8, 8, 12, .93); }

/* contain, never cover: this is the one place in the app where seeing all of a
   picture matters more than filling a space with it. */
.lb-img {
  grid-row: 1; max-width: 100vw; max-height: calc(100dvh - 64px);
  width: auto; height: auto; object-fit: contain; display: block;
  cursor: zoom-out;
  /* We do the panning and the pinching, so the browser must not also try:
     without this a one-finger drag scrolls the page behind and a pinch zooms
     the whole document, both underneath a picture that is trying to move. */
  touch-action: none;
  will-change: transform;
  /* The same job as draggable="false" in the markup, for the engines that want
     it said in CSS: a picture being panned must not also be a picture being
     dragged out of the page. */
  -webkit-user-drag: none;
  user-select: none; -webkit-user-select: none;
}
/* Zoomed, the picture is something you move rather than something you dismiss —
   and the cursor is the only warning that a click has stopped closing it. */
.lb-img.zoomed { cursor: grab; }
.lb-img.dragging { cursor: grabbing; }
.lb-img.alpha-bg { --sq: 16px; }

.lb-foot {
  grid-row: 2; width: 100%;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
  color: #eceaf2;
  /* Positioned so it survives being zoomed over. A transformed element paints
     with the positioned descendants, above ordinary block siblings — so once
     the picture was scaled past the window it covered this whole row, and the
     zoom controls disappeared at exactly the moment they became useful. */
  position: relative; z-index: 2;
  /* And a scrim, because what is behind it is now artwork rather than black,
     and a filename over the pale end of a gradient is unreadable. */
  background: linear-gradient(to top, rgba(8, 8, 12, .9), rgba(8, 8, 12, 0));
}
.lb-what { display: flex; flex-direction: column; gap: 2px; min-width: 0; margin-right: auto; }

/* Zoom controls. Quiet, but on screen — a gesture nobody is told about is a
   feature nobody has. */
.lb-zoom {
  display: flex; align-items: center; gap: 2px; flex: none;
  background: rgba(28, 28, 36, .82); border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 999px; padding: 2px;
}
.lb-zbtn, .lb-zlevel {
  background: none; border: 0; color: #eceaf2; cursor: pointer;
  font: inherit; border-radius: 999px; -webkit-tap-highlight-color: transparent;
}
.lb-zbtn { width: 34px; height: 34px; font-size: 17px; line-height: 1; }
.lb-zlevel {
  min-width: 58px; height: 34px; font-size: 12.5px;
  font-variant-numeric: tabular-nums; color: #9a9aa8;
}
.lb-zbtn:hover, .lb-zlevel:hover { background: rgba(255, 255, 255, .12); color: #eceaf2; }
.lb-zbtn:disabled { opacity: .35; cursor: default; background: none; }
.lb-what strong { font-size: 13.5px; overflow-wrap: anywhere; }
.lb-what span { font-size: 12px; color: #9a9aa8; font-variant-numeric: tabular-nums; }

.lb-close {
  position: fixed; top: calc(10px + env(safe-area-inset-top)); right: 12px; z-index: 2;
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; font-size: 15px; line-height: 1;
  background: rgba(28, 28, 36, .82); color: #eceaf2;
  border: 1px solid rgba(255, 255, 255, .16);
  -webkit-tap-highlight-color: transparent;
}
.lb-close:hover { background: rgba(48, 48, 60, .92); }

/* Along the row. Same disc as the close button, on the edges rather than in the
   foot: the foot is where the picture's own controls live, and next/previous is
   about the result set rather than about this picture. Fixed, so they stay put
   while a zoomed picture moves underneath them — and z-index 2 for the reason
   the foot gives, that a transformed image paints above its ordinary siblings
   and would otherwise cover them at exactly the moment somebody is comparing. */
.lb-nav {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 44px; height: 44px; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; font-size: 24px; line-height: 1;
  background: rgba(28, 28, 36, .82); color: #eceaf2;
  border: 1px solid rgba(255, 255, 255, .16);
  -webkit-tap-highlight-color: transparent;
}
.lb-nav.prev { left: 12px; }
.lb-nav.next { right: 12px; }
.lb-nav:hover { background: rgba(48, 48, 60, .92); }
/* Dimmed rather than removed at the ends of the set: a control that disappears
   takes the answer to "was there another one?" with it. */
.lb-nav:disabled { opacity: .3; cursor: default; background: rgba(28, 28, 36, .82); }

/* The panel's picture, made to look like something you can open. */
.bigwrap {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in; border-radius: var(--radius);
}
.bigwrap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bigwrap:hover .big { filter: brightness(1.06); }
/* ---------- typing on a phone must not move the page ----------
   iOS Safari zooms the whole page in whenever it opens the keyboard under a
   control whose text is smaller than 16px, and it does not zoom back out
   afterwards. On a laptop that would be a curiosity; here it is the difference
   between a working screen and a broken one, because the app shell is a fixed
   100dvh frame with overflow: hidden — once the page is scaled up there is no
   edge left to scroll to, and the inspector's Save button, the close button and
   the selection bar are simply off the side of the screen for good.

   So every control that can take focus is 16px below the desktop layouts. That
   is the whole fix, and it is the right one: the alternative everybody reaches
   for — maximum-scale=1, user-scalable=no in the viewport tag — stops the zoom
   by taking pinch-to-zoom away from the reader as well, and this archive is
   used by someone who zooms into artwork on purpose.

   These selectors deliberately mirror the ones above rather than relying on
   bare `input`/`select`: a rule like `.dlg input` outranks the element on its
   own, and a fix that silently loses to the rule it is fixing is worse than no
   fix. Source order settles the ties. */
@media (max-width: 900px), (pointer: coarse) {
  input, select, textarea, .selectish,
  #q, #invlink, #mcpurl, #mcpurl2, #mcpauth, #sharelink,
  .edit input, .edit select,
  .dlg input, .dlg select,
  .pickfilters #pickq,
  .sharerow-link input { font-size: 16px; }

  /* 16px no longer fits the shorter boxes those two rules pin by hand. */
  .edit select, .dlg select { height: var(--control-h); }
}

/* ---- a deck in the grid ------------------------------------------------- */
/* Not a picture. It gets a face of its own rather than a broken thumbnail, and
   a real cover render arrives with publishing — a deck's appearance is a thing
   we can only know by drawing it. */
.deckcard { cursor: pointer; }
.deckcard .shot {
  background: linear-gradient(150deg, var(--panel-2), var(--panel));
  display: flex; align-items: center; justify-content: center;
}
.deckface { font-size: 44px; color: var(--accent); opacity: .85; line-height: 1; }
.deckcard:hover .shot { outline: 2px solid var(--accent); outline-offset: -2px; }
.badge.deckbadge { background: var(--accent); color: #fff; }


/* ---- the crop window, and the frame window ------------------------------- */
/* A <dialog> for the reasons the lightbox is one, but the opposite shape: this
   is a panel with a picture in it rather than a picture with controls over it.
   The frame matters here — you are judging an edge against something, and an
   edge against black is not the same edge.

   The frame grab borrows this shell and its head, foot and buttons, because it
   is the same shape of thing: a picture to look at, a row of small adjustments,
   and one decision at the end. Only the stage differs, and only because a video
   element sizes itself differently from an img — see .frame-stage below. */
.cropper, .framer {
  color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0; max-width: min(94vw, 960px); max-height: 92dvh;
  overflow: hidden; display: flex; flex-direction: column;
}
.cropper:not([open]), .framer:not([open]) { display: none; }
.cropper::backdrop, .framer::backdrop { background: rgba(8, 8, 12, .72); }

.crop-head {
  display: flex; align-items: baseline; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid var(--line); min-width: 0;
}
.crop-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Pushed to the far end and never allowed to wrap: it changes on every pointer
   move, and a number that reflows the row it is in is a number that makes the
   whole header twitch for the length of a drag. */
.crop-out { margin-left: auto; flex: none; color: var(--ink-dim); font-variant-numeric: tabular-nums; }

/* Shrink-wrapped to the picture, and that is load-bearing rather than tidy: the
   rectangle is positioned in PERCENTAGES of this box, so the box has to be
   exactly the picture and not the space around it. A stage with letterboxing in
   it would put the crop somewhere else than where it is drawn. */
.crop-stage {
  position: relative; align-self: center; margin: 16px;
  line-height: 0; touch-action: none; user-select: none; -webkit-user-select: none;
  /* Under the picture, so a crop of something with transparency in it is judged
     against the same checkerboard the rest of the app uses. */
  background:
    conic-gradient(from 90deg, var(--panel-2) 0 25%, var(--panel) 0 50%) 0 0 / 16px 16px;
}
.crop-img {
  display: block; max-width: min(86vw, 900px); max-height: 56dvh;
  width: auto; height: auto; -webkit-user-drag: none;
}

/* The rectangle. Everything outside it is dimmed by a shadow spread far enough
   to cover any picture, which is one element instead of the four-div mask this
   would otherwise be — and it cannot leave a seam, because there are no edges in
   it to meet. */
.crop-box {
  position: absolute; cursor: move;
  outline: 1px solid rgba(255, 255, 255, .9);
  box-shadow: 0 0 0 100vmax rgba(8, 8, 12, .55);
  /* The thirds, which is the whole reason anyone wants to see the box rather
     than just its edges. Drawn with gradients so the guides cost no elements. */
  background:
    linear-gradient(to right, transparent calc(33.333% - .5px), rgba(255, 255, 255, .35) calc(33.333% - .5px) calc(33.333% + .5px), transparent calc(33.333% + .5px)),
    linear-gradient(to right, transparent calc(66.667% - .5px), rgba(255, 255, 255, .35) calc(66.667% - .5px) calc(66.667% + .5px), transparent calc(66.667% + .5px)),
    linear-gradient(to bottom, transparent calc(33.333% - .5px), rgba(255, 255, 255, .35) calc(33.333% - .5px) calc(33.333% + .5px), transparent calc(33.333% + .5px)),
    linear-gradient(to bottom, transparent calc(66.667% - .5px), rgba(255, 255, 255, .35) calc(66.667% - .5px) calc(66.667% + .5px), transparent calc(66.667% + .5px));
}

/* 22px, not the 8px the eye sees. The visible square is drawn by ::after; the
   element itself is a finger-sized target, because this window opens on the
   phone the photographs were taken on. */
.crop-grip { position: absolute; width: 22px; height: 22px; touch-action: none; }
.crop-grip::after {
  content: ''; position: absolute; inset: 7px;
  background: #fff; border: 1px solid rgba(8, 8, 12, .5); border-radius: 2px;
}
.crop-nw { top: -11px; left: -11px; cursor: nwse-resize; }
.crop-ne { top: -11px; right: -11px; cursor: nesw-resize; }
.crop-se { bottom: -11px; right: -11px; cursor: nwse-resize; }
.crop-sw { bottom: -11px; left: -11px; cursor: nesw-resize; }
.crop-n  { top: -11px; left: 50%; margin-left: -11px; cursor: ns-resize; }
.crop-s  { bottom: -11px; left: 50%; margin-left: -11px; cursor: ns-resize; }
.crop-w  { left: -11px; top: 50%; margin-top: -11px; cursor: ew-resize; }
.crop-e  { right: -11px; top: 50%; margin-top: -11px; cursor: ew-resize; }

/* Not shrink-wrapped, unlike .crop-stage, and it does not need to be: nothing is
   positioned in percentages of this box. The black is deliberate — a frame is
   judged as a picture, and letterboxing against the panel colour would read as
   part of the shot. */
.frame-stage {
  align-self: center; margin: 16px; line-height: 0; background: #000;
  border-radius: calc(var(--radius) - 4px); overflow: hidden;
}
.frame-video {
  display: block; max-width: min(86vw, 900px); max-height: 56dvh;
  width: auto; height: auto;
}

.crop-foot {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}
.crop-ratios { display: flex; gap: 4px; flex-wrap: wrap; margin-right: auto; }
.crop-ratio {
  height: var(--control-h); padding: 0 12px;
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 999px;
  font: inherit; cursor: pointer;
}
.crop-ratio:hover { border-color: var(--accent); }
.crop-ratio.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.crop-buttons { display: flex; gap: 8px; flex: none; }
