/* Cartoonize.

   The bars, the dialogs and the full screen button are arcade.css doing its
   job -- this page carries .arcade-page and .ar-shell and inherits the lot. It
   is not a game, but the frame around it is the same frame, and the whole
   point of putting that frame in one file was to stop the next page copying
   it.

   What is genuinely its own is here: the sliders, the picture chooser, and a
   stage that has to letterbox a photograph rather than fill itself with a
   board. Sized for the same readers as the games -- nothing under 1rem,
   nothing shorter than 2.6rem. */

/* ------------------------------------------------------------- the stage

   Deliberately NOT .ar-stage. That one pins its canvas to inset:0 and stretches
   it to fill, which is exactly right for a board drawn to whatever shape it is
   given and exactly wrong for a photograph -- it would squash every portrait
   picture into the shape of the window.

   The canvas therefore has to be held by max-width and max-height with its own
   width and height left auto, which is the one arrangement where the browser
   keeps a picture's shape for you. The catch is that a percentage max-height
   only resolves against a parent whose height is already settled, and the
   stage's height is NOT settled: it is a flex item that grows to fill what is
   left. Ask it how tall it is and the honest answer is "as tall as what is
   inside me", so the percentage is circular, the browser drops it, and the
   canvas renders full size and pushes the footer off the bottom of the page.
   Which is exactly what it did.

   .ct-frame breaks the circle. It is positioned, so its height comes from the
   stage's edges rather than from its contents, and it contributes nothing back.
   The canvas inside it then has something definite to be a percentage of.

   Worth the extra element rather than object-fit on the canvas itself, which
   would also letterbox but would leave the canvas's layout box bigger than the
   picture drawn in it -- and the comparison seam is dragged by turning a mouse
   position into a fraction of that box. Every drag would land in the wrong
   place on any picture that did not exactly fill the stage. */

.ct-stage {
  position: relative;
  flex: 1;
  min-height: 17rem;
  border: 1px solid var(--rule-strong);
  border-radius: 12px;
  background: var(--ground);
  overflow: hidden;
}

.ct-frame {
  position: absolute;
  inset: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ct-frame canvas {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  /* A flex item's automatic minimum size is its content's intrinsic size -- a
     1200px canvas -- and that would win over both max rules above, leaving the
     picture hanging out of the frame on a narrow window. */
  min-width: 0;
  min-height: 0;
  border-radius: 4px;
  box-shadow: 0 2px 14px var(--shadow, rgba(0, 0, 0, 0.3));
  /* Every drag on the picture is the comparison seam. A browser that decides
     one was a scroll makes the seam unusable on a phone. */
  touch-action: none;
}

.ct-stage.is-comparing .ct-frame canvas { cursor: ew-resize; }

.ct-frame canvas:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

/* Which half is which, while the seam is showing. Only then: two labels
   permanently sitting on a picture would be two labels somebody has to look
   past every time they want to see what they have made. */
.ct-stage::before,
.ct-stage::after {
  position: absolute;
  top: 14px;
  padding: 3px 10px;
  border-radius: 20px;
  background: var(--backdrop, rgba(0, 0, 0, 0.55));
  color: #fff;
  font-family: 'Trebuchet MS', 'Segoe UI', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 140ms ease-out;
  pointer-events: none;
}

.ct-stage::before { left: 16px; content: 'Before'; }
.ct-stage::after { right: 16px; content: 'After'; }

.ct-stage.is-comparing::before,
.ct-stage.is-comparing::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ct-stage::before,
  .ct-stage::after { transition: none; }
}


/* ----------------------------------------------------------- the picture

   The chooser is a chip rather than a button with a caption, so the name of
   the picture being worked on is on the bar at all times. The thing people
   lose track of in an app like this is not which slider they moved, it is
   which of the four photographs they opened they are looking at. */

.ct-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 24rem;
  min-height: 2.6rem;
  padding: 4px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ct-chip:hover { background: var(--accent-soft); border-color: var(--accent); }

.ct-chip__icon { color: var(--accent); font-size: 1.1rem; }

.ct-chip__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.ct-chip__text b,
.ct-chip__text i {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ct-chip__text i {
  font-size: 0.8rem;
  font-style: normal;
  color: var(--ink-faint);
}

.ct-chip__caret { margin-left: auto; color: var(--ink-faint); }


/* ---------------------------------------------------------- the sliders

   A row of their own rather than squeezed onto a bar. Five sliders on the
   control bar wrapped it to four rows on anything under a laptop, and a
   slider that moves to a different line every time a label changes length is
   one people stop using.

   accent-color does the theming, which is worth saying out loud because the
   alternative is forty lines of ::-webkit-slider-thumb that has to be written
   again for every engine and looks wrong on all of them. One line, themed,
   and the native control keeps its own keyboard behaviour. */

.ct-knobs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 6px 18px;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
}

.ct-knob {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0 8px;
}

.ct-knob__name {
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.ct-knob__value {
  font-size: 0.86rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.ct-knob input[type="range"] {
  grid-column: 1 / -1;
  width: 100%;
  /* The whole 2.6rem is the target. A 4px track is a 4px target on a trackpad,
     which for the people this site is built for is the difference between a
     control and an annoyance. */
  min-height: 2.6rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.ct-knob input[type="range"]:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 4px;
}


/* --------------------------------------------------------- what it is doing

   The readout says the picture's size, what the chosen look is for, and
   whatever the render is in the middle of. The last of those is the only one
   that moves, so it is last -- a status that appears and disappears in the
   middle of a line shoves everything after it sideways. */

.ct-blurb { font-style: italic; }

.ct-status { color: var(--ink-faint); }

.ct-status.is-bad { color: var(--danger); font-style: normal; }

/* A thread of accent along the top of the stage while the worker is busy.
   Not a spinner and not a percentage: a render is a fifth of a second and
   anything that claims to measure it is lying. This says "something is
   happening", which is all that is true. */
.is-working .ct-stage {
  background-image: linear-gradient(90deg,
    transparent 0%, var(--accent) 50%, transparent 100%);
  background-size: 40% 3px;
  background-repeat: no-repeat;
  background-position: 0 0;
  animation: ct-sweep 1.1s linear infinite;
}

@keyframes ct-sweep {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .is-working .ct-stage {
    animation: none;
    background-position: 30% 0;
  }
}


/* -------------------------------------------------------- nothing open yet

   What the page looks like before a picture has been chosen, and what it
   falls back to when the gallery is empty or did not answer. It is a door,
   not an error. */

/* The [hidden] rule has to be here rather than left to the browser. A class
   selector setting display:flex beats the user agent's own [hidden] rule, so
   without this the invitation stays sitting on top of the picture the moment
   one is opened -- which is exactly what it did. The same trap is why .ct-busy
   and .ct-drop below spell it out too. */
.ct-empty[hidden] { display: none; }

.ct-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px;
  text-align: center;
  color: var(--ink-muted);
}

.ct-empty h2 {
  margin: 0;
  color: var(--accent);
  font-size: calc(1.4rem * var(--font-scale, 1));
}

.ct-empty p { margin: 0; max-width: 28rem; line-height: 1.55; }

.ct-empty__big {
  min-height: 3.2rem;
  padding: 0 1.6rem;
  border: 1px solid var(--accent-strong);
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: calc(1.05rem * var(--font-scale, 1));
  font-weight: bold;
  cursor: pointer;
}

.ct-empty__big:hover { filter: brightness(1.1); }


/* ------------------------------------------------------------- overlays */

.ct-drop,
.ct-busy {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  font-size: calc(1.15rem * var(--font-scale, 1));
  font-weight: bold;
}

.ct-drop {
  border: 3px dashed var(--accent);
  border-radius: 12px;
  background: var(--accent-soft);
  color: var(--accent);
  /* The hint is shown from a dragover handler on the shell. If it could take
     the pointer itself it would swallow the dragleave that hides it again,
     and the page would be left with a permanent blue box over it. */
  pointer-events: none;
}

.ct-busy {
  background: var(--backdrop, rgba(0, 0, 0, 0.45));
  color: #fff;
}

.ct-busy[hidden],
.ct-drop[hidden] { display: none; }


/* ---------------------------------------------------- choosing a picture */

.ct-picker { width: min(52rem, calc(100vw - 32px)); }

.ct-picker__own {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 12px 0;
  padding: 14px;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--accent-soft);
}

.ct-picker__own p {
  flex: 1;
  min-width: 14rem;
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* A label, styled as the button, with the real input inside it and visually
   hidden. A file input cannot be styled and cannot be opened from script
   without a gesture; this is the one arrangement that gets a button that looks
   like the rest of the page AND keeps the input's own keyboard and screen
   reader behaviour. */
.ct-file {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 3rem;
  padding: 0 1.4rem;
  border: 1px solid var(--accent-strong);
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: calc(1rem * var(--font-scale, 1));
  font-weight: bold;
  cursor: pointer;
}

.ct-file:hover { filter: brightness(1.1); }

.ct-file:focus-within {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.ct-file input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.ct-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 10px;
  max-height: 52vh;
  overflow-y: auto;
  padding: 2px;
}

.ct-pick {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  border: 2px solid var(--rule);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

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

.ct-pick img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--surface-alt);
}

.ct-pick__label {
  padding: 5px 7px;
  font-size: 0.82rem;
  line-height: 1.3;
  text-align: left;
  color: var(--ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ------------------------------------------------------------ narrow windows

   The sliders go to one column before they go illegible. Two cramped columns
   of slider is worse than one comfortable one and a scroll. */

@media (max-width: 560px) {
  .ct-knobs { grid-template-columns: 1fr; }
  .ct-chip { max-width: 100%; }
}

@media (max-height: 560px) {
  .ct-knobs { border: 0; padding: 0 2px; background: none; }
  .ct-stage { min-height: 12rem; }
}
