/* css/island-activity.css — the island answering.

   Every press on the site that changes something is answered here, on the
   one object the whole site is arranged around. The island closes over its
   own contents and blooms into a capsule: a glyph on the reading side, what
   happened, what it happened to, and a figure on the far side when there is
   one worth reading. Then it settles back into the island it was.

   The shape and its timing are js/island.js's (a spring, read from wherever
   the plate is mid-flight). This file draws what sits inside the capsule and
   the small move each glyph makes as it lands — the ribbon drops in filled,
   the two links close on each other, the paper feeds out of the printer —
   so the glyph acts out the thing the words say.

   The tones are the system's: yellow for what the student chose to keep,
   blue for what the site did for them, white for a change of state, and red
   for the one thing that did not work.
   ========================================================================== */

.island {
  /* The island is already black; the capsule is the same black, so an
     activity is a change of shape and nothing else — exactly as on a phone. */
  --act-ink: var(--nav-ocean, #000);
  --act-ink-edge: var(--nav-edge, #000);
  --act-ink-shadow: var(--nav-shadow, 0 14px 34px rgba(0, 0, 0, 0.3));
  /* The capsule is the island plus the room a second line needs. It grows
     down from the island's top edge, the way the island grows out of the top
     of a phone. */
  --act-h: calc(var(--header-bar-h) + 8px);

  /* The system's colours on black. */
  --act-title: #ffffff;
  --act-detail: #98989f;
  --act-code: #e5e5ea;
  --act-brass: #ffd60a;
  --act-sea: #0a84ff;
  --act-buoy: #ff453a;
  --act-quiet: #ffffff;
}

/* While the capsule is up, the plate under it cannot be pressed: it is
   faded out and blurred, and a tap that landed on an invisible button would
   be the island doing something nobody asked it to. */
.island.is-activity .island-bar,
.island.is-activity .island-drawer {
  pointer-events: none;
}

/* ------------------------------------------------------------- the capsule */

.island-activity {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;

  display: flex;
  align-items: center;
  gap: 12px;
  /* Its own content decides its width — js/island.js measures this and then
     pins it, so the surface has a number to travel to. */
  width: max-content;
  min-width: min(15.5rem, calc(100vw - 20px));
  max-width: min(29rem, calc(100vw - 20px));
  height: var(--act-h);
  padding-inline: 11px 22px;

  color: var(--act-title);
  pointer-events: auto;
  cursor: default;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* On its way out it is only a picture of itself: the island under it is
   already the island again, and a press in those last few hundred
   milliseconds belongs to the island, not to a capsule that has gone. */
.island:not(.is-activity) .island-activity { pointer-events: none; }

.island-activity.can-tap { cursor: pointer; }

.island-activity[data-tone="brass"] { --tone: var(--act-brass); }
.island-activity[data-tone="sea"]   { --tone: var(--act-sea); }
.island-activity[data-tone="quiet"] { --tone: var(--act-quiet); }
.island-activity[data-tone="buoy"]  { --tone: var(--act-buoy); }
/* The timer's own colours, as the phone's clock app draws them. */
.island-activity[data-tone="orange"] { --tone: #ff9f0a; }
.island-activity[data-tone="green"]  { --tone: #30d158; }

/* The glyph, in a disc of its own tone. The disc is what lets a glyph read
   at a glance on a phone: the colour arrives before the drawing does. */
.island-activity-glyph {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: calc(var(--act-h) - 22px);
  height: calc(var(--act-h) - 22px);
  border-radius: 50%;
  color: var(--tone, var(--act-quiet));
  background: color-mix(in srgb, var(--tone, var(--act-quiet)) 17%, transparent);
}

.act-svg {
  width: 56%;
  height: 56%;
  overflow: visible;
}

.act-svg * {
  transform-box: fill-box;
  transform-origin: center;
}

.island-activity-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  flex: 1 1 auto;
  min-width: 0;
}

.island-activity-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1.35;
  color: var(--act-title);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.island-activity-detail {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--act-detail);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The first line of what was copied, as code: it is the one detail that
   tells you *which* of eighty-nine blocks went to the clipboard. Left to
   right, and ruled to the same edge as the Arabic above it. */
.island-activity-detail.is-code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--act-code);
  text-align: right;
}

.island-activity-meta {
  flex: none;
  padding-inline-start: 6px;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.8rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--tone, var(--act-quiet));
  white-space: nowrap;
}

/* For assistive tech only. The capsule is aria-hidden; this says it once. */
.island-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------- the glyph moves

   Each one plays once, as the capsule lands, and each is the event itself
   rather than a generic entrance. The glyph's nodes are drawn fresh for
   every activity, so a second copy replays its move from the start. */

/* A stroke that draws itself: pathLength="1" on the path makes the dash
   one unit long whatever the shape. Only inside the animation — a browser
   that plays none of this still draws the stroke whole. */
.act-draw {
  animation: act-draw 460ms 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes act-draw {
  from { stroke-dasharray: 1 1; stroke-dashoffset: 1; opacity: 0; }
  12%  { opacity: 1; }
  to   { stroke-dasharray: 1 1; stroke-dashoffset: 0; opacity: 1; }
}

/* Kept: the ribbon drops onto the part, already filled, with a stamp's
   rebound. And a single ring goes out from the disc — the one moment on the
   site brass is allowed to move. */
.act-ribbon {
  fill: currentColor;
  animation: act-ribbon 560ms 130ms cubic-bezier(0.2, 1.22, 0.4, 1) both;
}

@keyframes act-ribbon {
  from { transform: translateY(-6px) scale(0.6); fill-opacity: 0; }
  55%  { fill-opacity: 1; }
  to   { transform: none; fill-opacity: 1; }
}

.act-ping {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--tone);
  pointer-events: none;
  animation: act-ping 900ms 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes act-ping {
  from { opacity: 0.8; transform: scale(0.9); }
  to   { opacity: 0; transform: scale(1.42); }
}

/* Unkept: the fill drains out of the ribbon and a stroke crosses it off. */
.act-ribbon-off {
  animation: act-drain 520ms 120ms ease-out both;
}

@keyframes act-drain {
  from { fill: currentColor; fill-opacity: 0.9; }
  to   { fill: currentColor; fill-opacity: 0; }
}

/* A link: the two halves of the chain close on each other. */
.act-link-a { animation: act-link-a 540ms 150ms cubic-bezier(0.2, 1.25, 0.4, 1) both; }
.act-link-b { animation: act-link-b 540ms 150ms cubic-bezier(0.2, 1.25, 0.4, 1) both; }

@keyframes act-link-a { from { transform: translate(3.5px, -3.5px); opacity: 0.2; } to { transform: none; opacity: 1; } }
@keyframes act-link-b { from { transform: translate(-3.5px, 3.5px); opacity: 0.2; } to { transform: none; opacity: 1; } }

/* Copied: the front sheet lands on the one behind it. */
.act-sheet { animation: act-sheet 520ms 140ms cubic-bezier(0.2, 1.22, 0.4, 1) both; }

@keyframes act-sheet {
  from { transform: translate(-4px, -4px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* Print: the page feeds out of the printer. */
.act-paper { animation: act-paper 720ms 220ms cubic-bezier(0.16, 1, 0.3, 1) both; }

@keyframes act-paper {
  from { transform: translateY(-5px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* The network. Coming back, the arcs light from the centre out; going,
   they dim and a stroke crosses them out. */
.act-offline .act-arc { opacity: 0.4; }

.act-online .act-arc { animation: act-arc 380ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.act-online .act-arc-1 { animation-delay: 180ms; }
.act-online .act-arc-2 { animation-delay: 270ms; }
.act-online .act-arc-3 { animation-delay: 360ms; }

@keyframes act-arc {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: none; }
}

/* The account: the head arrives, the shoulders are drawn under it. */
.act-head { animation: act-head 520ms 120ms cubic-bezier(0.2, 1.25, 0.4, 1) both; }

@keyframes act-head {
  from { transform: scale(0.4); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* The filter settles like something poured through it. */
.act-funnel { animation: act-funnel 560ms 140ms cubic-bezier(0.2, 1.22, 0.4, 1) both; }

@keyframes act-funnel {
  from { transform: translateY(-3px) scaleY(0.7); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* The whole lesson back: the lines run out to their full length. */
.act-line {
  transform-origin: left center;
  animation: act-line 460ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.act-line-1 { animation-delay: 160ms; }
.act-line-2 { animation-delay: 230ms; }
.act-line-3 { animation-delay: 300ms; }

@keyframes act-line {
  from { transform: scaleX(0.15); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* Something did not work: the glyph shakes its head, once. */
.island-activity.is-shaking .act-svg {
  animation: act-shake 440ms 300ms ease-out both;
}

@keyframes act-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-2.5px); }
  40% { transform: translateX(2.5px); }
  60% { transform: translateX(-1.5px); }
  80% { transform: translateX(1.5px); }
}

/* ------------------------------------------------------------- the ring

   How far through something the student is — a lesson, a download — drawn
   on the disc's own edge, the way a phone draws a timer or a download in
   its island. With a ring the disc gives up its fill: the ring is the
   colour, and the glyph inside is drawn smaller so the two do not touch. */
.island-activity-glyph.has-ring { background: transparent; }
.island-activity-glyph.has-ring .act-svg { width: 64%; height: 64%; stroke-width: 2.4; }

.act-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  transform: rotate(-90deg);
  pointer-events: none;
}

.act-ring circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
}

.act-ring-track { opacity: 0.22; }

.act-ring-run {
  stroke-linecap: round;
  stroke-dasharray: 1 1;
  /* A live activity moves its ring on; the move is eased, not stepped. */
  transition: stroke-dashoffset 520ms cubic-bezier(0.16, 1, 0.3, 1);
  /* And it arrives by running out from nothing to where it stands. */
  animation: act-ring 900ms 200ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

@keyframes act-ring {
  from { stroke-dashoffset: 1; }
}

/* Resume: the play mark eases in behind the ring. */
.act-play {
  stroke-width: 1.4;
  animation: act-head 520ms 360ms cubic-bezier(0.2, 1.25, 0.4, 1) both;
}

/* Downloading: the arrow keeps dropping into the tray while it runs. */
.act-drop { animation: act-drop 1400ms 500ms cubic-bezier(0.45, 0, 0.55, 1) infinite; }

@keyframes act-drop {
  0%, 100% { transform: translateY(-1.5px); }
  50%      { transform: translateY(1.5px); }
}

/* Done: the tick draws once the ring has closed. */
.act-done .act-draw { animation-delay: 620ms; }

/* A lesson finished: eight sparks thrown off the disc, once. They stop
   short of the capsule's edge — the celebration stays on the black. */
.act-spark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: var(--tone);
  opacity: 0;
  pointer-events: none;
  animation: act-spark 760ms 720ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.act-spark:nth-child(even) { background: var(--act-brass); }

@keyframes act-spark {
  0%   { opacity: 0; transform: rotate(var(--spark)) translateY(0) scale(0.4); }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--spark)) translateY(-24px) scale(1); }
}

/* ---------------------------------------------------------------- phones */

@media (max-width: 720px) {
  .island-activity {
    gap: 10px;
    padding-inline: 9px 18px;
  }

  .island-activity-glyph {
    width: calc(var(--act-h) - 20px);
    height: calc(var(--act-h) - 20px);
  }

  .island-activity-title { font-size: 0.85rem; }
  .island-activity-detail { font-size: 0.71rem; }
  .island-activity-meta { font-size: 0.74rem; }
}

/* Reduced motion keeps the news and drops the travel: the capsule appears
   and leaves by fading (js/island.js), and the glyphs arrive already drawn. */
@media (prefers-reduced-motion: reduce) {
  .island-activity *,
  .island-activity.is-shaking .act-svg {
    animation: none !important;
  }
  .act-ping,
  .act-spark { display: none; }
  .act-ring-run { transition: none; }
}
