/*
  One stylesheet for the whole application. The content security policy allows
  no inline style and no external resources at all, so everything visual lives
  here, the fonts are the ones the operating system already has, and there is no
  build step: plain CSS with custom properties and no preprocessor syntax.

  It reads in one direction, so a rule's neighbours are the rules it interacts
  with: design tokens, then the base elements, then the two page layouts, then
  the components, then the breakpoints. Everything is mobile-first; the media
  queries are all min-width and only ever add.

  Colour lives in the tokens and nowhere else. Every component names a role
  (--surface, --fg-muted, --danger) rather than a colour, so the light and dark
  palettes are two blocks at the top of the file and no component rule has to
  know which one is in force.

  Two layouts share it. `.card` is the narrow centred box the sign-in, invite,
  passcode and error pages use. `.shell` is the full-width application chrome
  the dashboard, upload and share pages use.
*/

/* Tokens ------------------------------------------------------------------ */

:root {
  /* Light is the default and dark overrides it below, so a browser that
     reports no preference gets the light palette. color-scheme also tells the
     browser to draw its own widgets (scrollbars, the select's arrow, the date
     picker) to match, which is why none of those need styling here. */
  color-scheme: light dark;

  --bg: #f6f8fa;
  --surface: #ffffff;
  --sunken: #eceff3;
  --line: #d8dee6;
  --line-strong: #79838f;
  --fg: #10161c;
  --fg-muted: #56626f;
  --accent: #0f62d4;
  --accent-hover: #0a4ea8;
  --accent-active: #083d84;
  --accent-fg: #ffffff;
  --accent-soft: #e8f0fe;
  --ok: #0f7a53;
  --ok-soft: #e3f4ec;
  --warn: #8a5a00;
  --warn-soft: #fdf1d8;
  --danger: #b42318;
  --danger-soft: #fdeae8;
  --focus: #0f62d4;

  /* Depth. In light the surfaces are all near-white, so a shadow is the only
     thing that separates them; in dark a black shadow reads as dirt, so the
     tokens go to none there and the surface step plus a brighter hairline
     carries it instead. Every elevated component sets both. */
  --el-1: 0 1px 2px rgba(16, 22, 28, 0.06), 0 1px 3px rgba(16, 22, 28, 0.04);
  --el-2: 0 4px 12px rgba(16, 22, 28, 0.1);
  --el-3: 0 6px 16px rgba(16, 22, 28, 0.08);

  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --fs-xs: 0.75rem;
  --fs-sm: 0.875rem;
  --fs-base: 1rem;
  --fs-lg: 1.125rem;
  --fs-xl: clamp(1.375rem, 1.1rem + 1.4vw, 1.75rem);
  --fs-display: clamp(1.75rem, 1.3rem + 2.6vw, 2.5rem);

  --lh-tight: 1.2;
  --lh-snug: 1.35;
  --lh-base: 1.5;
  --lh-relaxed: 1.6;

  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;

  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;

  /* The smallest thing a finger can reliably hit. Every button, input, select
     and link-shaped button is at least this tall, including the small ones,
     which give up horizontal padding rather than height. */
  --tap: 2.75rem;

  --shell-max: 64rem;
  --gutter: var(--s-4);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0e1216;
    --surface: #171c22;
    --sunken: #0a0d11;
    --line: #262d36;
    --line-strong: #626e7c;
    --fg: #e9eef4;
    --fg-muted: #a3b0bd;
    --accent: #4fa3ff;
    --accent-hover: #7cbcff;
    --accent-active: #a8d3ff;
    --accent-fg: #08131f;
    --accent-soft: #152438;
    --ok: #43d19e;
    --ok-soft: #0f2a20;
    --warn: #ffc857;
    --warn-soft: #2b2313;
    --danger: #ff7b72;
    --danger-soft: #33191a;
    --focus: #8cc4ff;

    --el-1: none;
    --el-2: none;
    --el-3: 0 1px 0 var(--line);
  }
}

/* A reader who has asked for more contrast gets the decorative hairlines and
   the muted text promoted; nothing else about the layout changes. */
@media (prefers-contrast: more) {
  :root {
    --line: var(--line-strong);
    --fg-muted: var(--fg);
  }
}

/* Base -------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/*
  The `display` rules further down are author origin and so outrank the user
  agent's `[hidden] { display: none }`. Without this, every element the pages
  show and hide through the attribute (the create-link forms, the copy rows,
  the Revoke buttons) would be stuck on screen and its toggle would be inert.
  It carries !important because those rules are exactly what it has to beat.
*/
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--fg);
  font: var(--fs-base) / var(--lh-base) var(--font-sans);
  -webkit-text-size-adjust: 100%;
}

h1,
h2 {
  text-wrap: balance;
}

h1 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
}

/* The section heading is an eyebrow: small, spaced and quiet, so a panel's
   title never competes with the page's. The transfer card opts out below. */
h2 {
  margin: 0 0 var(--s-2);
  color: var(--fg-muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--accent-hover);
}

p {
  margin: 0 0 var(--s-4);
}

p:last-child {
  margin-bottom: 0;
}

.lede {
  margin: 0 0 var(--s-5);
  max-width: 60ch;
  color: var(--fg-muted);
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
}

.muted {
  color: var(--fg-muted);
  font-size: var(--fs-sm);
}

.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* Visible to screen readers, not on screen. Used for the labels the layout has
   no room for and for the file input the styled label stands in for. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The first thing in the tab order on every rendered page: off screen until it
   is focused, then a real button pinned to the top corner. */
.skip-link {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 20;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  background: var(--accent);
  color: var(--accent-fg);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-150%);
}

.skip-link:focus-visible {
  color: var(--accent-fg);
  transform: translateY(0);
}

/* Layout ------------------------------------------------------------------ */

/* The card layout centres its box; the shell layout fills the width. Both pad
   away from the notch and the home indicator on a phone in landscape. */
.card-body {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  padding: var(--s-6) max(var(--gutter), env(safe-area-inset-right)) max(var(--s-6), env(safe-area-inset-bottom))
    max(var(--gutter), env(safe-area-inset-left));
}

/* The centred box: forms and short messages. */
.card {
  display: block;
  width: 100%;
  max-width: 32rem;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--el-1);
}

/* The application chrome: everything behind the sign-in and the share pages. */
.shell {
  display: block;
  width: 100%;
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: var(--s-5) max(var(--gutter), env(safe-area-inset-right)) var(--s-7)
    max(var(--gutter), env(safe-area-inset-left));
}

/* Header ------------------------------------------------------------------ */

/*
  On a phone the header is content like anything else and scrolls away: the two
  destinations in it wrap onto their own row, and a permanently pinned block
  that tall is a poor trade for a 375px screen. It becomes sticky at 48rem,
  where it fits on one line. There is nothing in it a page needs mid-scroll;
  the action a page is for is in its own heading row.
*/
.topbar {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.topbar-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  align-items: center;
  justify-content: space-between;
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: var(--s-2) max(var(--gutter), env(safe-area-inset-right)) var(--s-2)
    max(var(--gutter), env(safe-area-inset-left));
}

.brand {
  display: inline-flex;
  gap: var(--s-2);
  align-items: center;
  min-height: var(--tap);
  color: var(--fg);
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.brand:hover {
  color: var(--fg);
}

.brand-mark {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--accent);
}

/* On a phone the navigation takes a row of its own rather than being squeezed
   into what is left beside the brand, which would wrap it a word at a time. */
.topnav {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  align-items: center;
  font-size: var(--fs-sm);
}

.topnav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--fg-muted);
  text-decoration: none;
}

.topnav a:hover {
  color: var(--fg);
  text-decoration: underline;
}

/*
  Which is the row the signed-in address gives up, because keeping it there
  costs a second one: the destinations and the address together do not fit
  across a 375px screen. It comes back at 48rem. Sign out is beside it either
  way, so nothing becomes unreachable, only unstated.
*/
.topnav .who {
  display: none;
  max-width: 14rem;
  overflow: hidden;
  color: var(--fg-muted);
  font-size: var(--fs-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inline {
  display: inline-flex;
}

/* The page's title and the one thing it is for. Stacked on a phone, so a long
   transfer title gets the whole width instead of sharing it with a button. */
.page-head {
  display: grid;
  gap: var(--s-3);
  justify-items: start;
  margin-bottom: var(--s-5);
}

/* Buttons ----------------------------------------------------------------- */

button,
.button {
  display: inline-flex;
  gap: var(--s-2);
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 0 var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: var(--accent);
  color: var(--accent-fg);
  font: inherit;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, transform 80ms ease;
}

button:hover,
.button:hover {
  background: var(--accent-hover);
  color: var(--accent-fg);
}

button:active,
.button:active {
  background: var(--accent-active);
  transform: translateY(1px);
}

button[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

button[disabled]:hover {
  background: var(--accent);
}

/* Small buttons give up horizontal padding, never height: a 32px-tall control
   is a miss on a phone however little room the row has. */
button.small,
.button.small {
  padding: 0 var(--s-3);
  font-size: var(--fs-sm);
}

/*
  The secondary buttons: everything that is not the one thing the page is for.
  They are listed by data-action rather than given a class because the modules
  clone them from the templates the server rendered, so the action attribute is
  already there and says what the button does. A queued file has one button
  whose action the module swaps between remove and retry, and both belong to
  this group.
*/
[data-action="copy"],
[data-action="revoke"],
[data-action="remove"],
[data-action="retry"],
[data-action="toggle-link-form"] {
  border-color: var(--line-strong);
  background: var(--surface);
  color: var(--fg);
  font-weight: 500;
}

[data-action="copy"]:hover,
[data-action="revoke"]:hover,
[data-action="remove"]:hover,
[data-action="retry"]:hover,
[data-action="toggle-link-form"]:hover {
  background: var(--sunken);
  color: var(--fg);
}

/* Destructive, but not shouted: the confirmation dialog is what stops a
   mistake, so this only needs to be unmistakably the red one. */
[data-action="delete"] {
  border-color: var(--danger);
  background: transparent;
  color: var(--danger);
  font-weight: 500;
}

[data-action="delete"]:hover,
[data-action="delete"]:active {
  background: var(--danger-soft);
  color: var(--danger);
}

.linkish {
  min-height: var(--tap);
  padding: 0 var(--s-2);
  border-color: transparent;
  background: none;
  color: var(--accent);
  font-weight: 500;
  text-decoration: underline;
}

.linkish:hover,
.linkish:active {
  background: none;
  color: var(--accent-hover);
}

/* One focus ring for everything, never removed. It is drawn outside the
   element so it never moves the layout, and it follows the element's own
   corners. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Forms ------------------------------------------------------------------- */

.stack {
  display: grid;
  gap: var(--s-3);
  margin: 0 0 var(--s-5);
}

.field {
  display: grid;
  gap: var(--s-1);
}

label {
  color: var(--fg-muted);
  font-size: var(--fs-sm);
  font-weight: 500;
}

input,
select,
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
}

select {
  padding-right: var(--s-2);
}

/* The one multi-line control, so the one that needs padding on all four sides
   rather than a line centred in its own height. It grows downwards only: made
   wider by hand it would leave the grid column it sits in. */
textarea {
  padding: var(--s-2) var(--s-3);
  line-height: 1.5;
  resize: vertical;
}

input:hover,
select:hover,
textarea:hover {
  border-color: var(--fg-muted);
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

/* A field nobody can type into is tinted like the wells, so it does not look
   like an input waiting for something. */
input:read-only {
  background: var(--sunken);
  color: var(--fg-muted);
}

/* The mono read-only fields are the minted invite and share URLs, which are
   there to be copied rather than filled in, and the dashed edge says so. The
   invite form's fixed email is read-only too and gets no such invitation. */
input.mono:read-only {
  border-style: dashed;
}

.row-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}

.row-form input {
  flex: 1 1 12rem;
}

.copy-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
  margin-bottom: var(--s-3);
}

.copy-row input {
  flex: 1 1 14rem;
}

.link-form {
  display: grid;
  gap: var(--s-3);
  align-items: end;
  padding-top: var(--s-4);
  margin-top: var(--s-4);
  border-top: 1px solid var(--line);
}

/* The "send this link by email" form, which folds out of a link row on the
   dashboard and sits under the minted link on the upload page. Inside a link
   row it spans every column, so the row's own grid areas are undisturbed by
   it and it reads as a panel that opened beneath them. */
.email-form {
  display: grid;
  gap: var(--s-3);
  align-items: end;
  grid-column: 1 / -1;
  padding-top: var(--s-3);
  margin-top: var(--s-2);
  border-top: 1px dashed var(--line);
}

/* A line of prose inside one of those grids, which is the upload page saying
   why its Create link button is disabled, or a send form reporting what
   happened. It spans the row, so the button underneath keeps the first column
   whether the line is showing or not. */
.form-note {
  grid-column: 1 / -1;
  margin: 0;
}

/* A form's own outcome line, which is a success far more often than not, so it
   is quiet by default and only takes the danger colour when it has to. It
   carries no icon: email.js sets its textContent, which would replace one. */
.form-note[data-state="error"] {
  color: var(--danger);
}

/*
  Every notice on every page is an error, so there is one recipe and no tone
  variants: a tinted panel with a heavier rule down the leading edge, which is
  what a reader sees before they read a word of it. The text is --fg rather
  than --danger so the message itself stays comfortable to read.

  It carries no icon on purpose. The three notices the modules drive are filled
  in with textContent, which replaces the element's children, so an icon would
  survive on the server-rendered ones and vanish from the rest.
*/
.notice {
  margin: 0 0 var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--danger);
  border-left-width: 3px;
  border-radius: var(--r-md);
  background: var(--danger-soft);
  color: var(--fg);
}

/* Panels and the transfer list -------------------------------------------- */

.panel {
  padding: var(--s-5);
  margin-bottom: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--el-1);
  /* The header is sticky, so anything scrolled to would otherwise land under
     it. */
  scroll-margin-top: 5rem;
}

.plain,
.transfers,
.links,
.queue,
.gallery {
  padding: 0;
  margin: 0;
  list-style: none;
}

.plain {
  color: var(--fg-muted);
  font-size: var(--fs-sm);
}

.plain li + li {
  margin-top: var(--s-1);
}

/* The transfer's own title is content, not an eyebrow, so it opts out of the
   section-heading treatment above. */
.transfer-head h2 {
  margin: 0 0 var(--s-1);
  color: var(--fg);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
}

.transfer-meta {
  margin: 0;
  color: var(--fg-muted);
  font-size: var(--fs-sm);
}

/*
  The links belonging to a transfer sit in a well, so a card with three of them
  still reads as one thing rather than three.

  The well is conditional because the list is rendered whether or not the
  transfer has a link yet: dashboard.js appends a cloned row into it, so it
  cannot be left out of the page and cannot be matched by :empty either, since
  the template leaves whitespace inside it. A browser without :has() loses the
  tint and keeps the rows, which is the right way round for decoration.
*/
.links:has(.link-row) {
  padding: var(--s-2) var(--s-3);
  margin: var(--s-4) 0;
  border-radius: var(--r-md);
  background: var(--sunken);
}

.link-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-2);
  align-items: center;
  padding: var(--s-3) 0;
}

.link-row + .link-row {
  border-top: 1px solid var(--line);
}

.link-row .badge {
  justify-self: start;
}

/* The URL field is read-only and so tinted like a well, which is also what it
   is sitting in; it takes the raised surface back so its edges are visible. */
.link-row input:read-only {
  background: var(--surface);
}

.link-meta {
  color: var(--fg-muted);
  font-size: var(--fs-xs);
}

.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.badge {
  display: inline-flex;
  gap: var(--s-2);
  align-items: center;
  padding: 0.2rem 0.55rem;
  border: 1px solid currentColor;
  border-radius: var(--r-pill);
  background: var(--sunken);
  color: var(--fg-muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The dot is decoration; the badge's own word is what says which state this
   is, so nothing here is carried by colour alone. It is a pseudo-element
   because dashboard.js sets the badge's textContent, which would wipe out any
   real child. */
.badge::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
}

.badge[data-status="active"] {
  background: var(--ok-soft);
  color: var(--ok);
}

.badge[data-status="expired"],
.badge[data-status="used up"] {
  background: var(--warn-soft);
  color: var(--warn);
}

.badge[data-status="revoked"] {
  background: var(--danger-soft);
  color: var(--danger);
}

.transfer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* Inside a transfer card the actions are ruled off, which also supplies the
   gap on a card that has no links yet and so no well above them. */
.transfer .transfer-actions {
  padding-top: var(--s-4);
  margin-top: var(--s-4);
  border-top: 1px solid var(--line);
}

/* Wide enough to look deliberate side by side on a phone, and allowed to wrap
   rather than to shrink below its own label. */
.transfer-actions button {
  flex: 1 1 8.5rem;
}

/* The empty states: a quiet dashed well with the way out of it inside. */
.empty {
  display: grid;
  gap: var(--s-3);
  justify-items: center;
  padding: var(--s-6) var(--s-4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--sunken);
  text-align: center;
}

.empty p {
  margin: 0;
  max-width: 40ch;
  color: var(--fg-muted);
  line-height: var(--lh-relaxed);
}

.empty .empty-title {
  color: var(--fg);
  font-size: var(--fs-base);
  font-weight: 600;
}

/* The way out reads as the next step rather than as a third line of prose, so
   it gets more air above it than the gap between the two paragraphs. */
.empty .button {
  margin-top: var(--s-2);
}

/* Upload ------------------------------------------------------------------ */

.dropzone {
  display: grid;
  gap: var(--s-3);
  justify-items: center;
  padding: var(--s-6) var(--s-4);
  margin: var(--s-4) 0;
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--sunken);
  text-align: center;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.dropzone[data-dragging="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: var(--el-2);
}

.dropzone-icon {
  width: 2rem;
  height: 2rem;
  color: var(--fg-muted);
}

.dropzone[data-dragging="true"] .dropzone-icon {
  color: var(--accent);
}

.dropzone-hint {
  margin: 0;
  color: var(--fg);
  font-weight: 500;
}

/*
  The file input itself is off screen and the label beside it is what a reader
  sees, so a keyboard focus ring would otherwise land on a 1px box nobody can
  see. The label comes first in the markup, so this has to look downwards from
  their shared parent rather than sideways. A browser without :has() is no
  worse off than it was.
*/
.dropzone:has(#file-input:focus-visible) .button {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.queue-item {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: var(--s-2) var(--s-3);
  align-items: center;
  padding: var(--s-3) 0;
  border-top: 1px solid var(--line);
}

.thumb {
  display: block;
  width: 48px;
  height: 48px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--sunken);
  object-fit: cover;
}

.queue-body {
  min-width: 0;
}

.queue-name,
.queue-size,
.queue-status {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.queue-name {
  font-weight: 500;
}

.queue-status {
  font-variant-numeric: tabular-nums;
}

.queue-status[data-state="error"] {
  color: var(--danger);
}

.queue-status[data-state="done"] {
  color: var(--ok);
}

.queue-action {
  grid-column: 2;
  justify-self: start;
}

/* A bare <progress> is drawn by the platform and ignores the palette, so both
   engines' parts are named. accent-color covers whatever else it paints. */
.queue-progress {
  appearance: none;
  width: 100%;
  height: 6px;
  margin: var(--s-1) 0;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--line);
  accent-color: var(--accent);
}

.queue-progress::-webkit-progress-bar {
  border-radius: var(--r-pill);
  background: var(--line);
}

.queue-progress::-webkit-progress-value {
  border-radius: var(--r-pill);
  background: var(--accent);
}

.queue-progress::-moz-progress-bar {
  border-radius: var(--r-pill);
  background: var(--accent);
}

/* Share ------------------------------------------------------------------- */

.share-summary {
  margin: 0 0 var(--s-5);
  color: var(--fg-muted);
  font-size: var(--fs-sm);
}

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: var(--s-4);
}

.tile {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--el-1);
}

.tile-preview {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--sunken);
}

.tile-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 160ms ease;
}

a.tile-preview:hover img {
  transform: scale(1.03);
}

.tile-generic {
  color: var(--fg-muted);
}

.tile-generic svg {
  width: 2rem;
  height: 2rem;
}

.tile-name {
  margin: 0;
  overflow: hidden;
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: var(--lh-snug);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tile-size {
  margin: 0;
  font-size: var(--fs-xs);
}

.tile .button {
  width: 100%;
}

/* Manual ------------------------------------------------------------------ */

/*
  The manual is the one page that is mostly prose, so it is the only place that
  needs rules for a run of steps, a bulleted list or a term and its definition.
  They are scoped to .prose so no application page inherits them.
*/
.prose {
  max-width: 68ch;
}

/* A manual section's title is content, not an eyebrow, so it opts out of the
   section-heading treatment above, exactly as a transfer's title does. */
.prose h2 {
  margin: 0 0 var(--s-3);
  color: var(--fg);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
}

.prose h3 {
  margin: var(--s-5) 0 var(--s-2);
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: var(--lh-snug);
}

.prose ol,
.prose ul {
  padding-left: var(--s-5);
  margin: 0 0 var(--s-4);
}

.prose li {
  margin-bottom: var(--s-2);
}

/* Term and definition, stacked. The two-column grid the landing page's status
   list uses would leave a definition a few words wide on a phone. */
.prose dl {
  display: block;
  margin: 0 0 var(--s-4);
}

.prose dt {
  color: var(--fg);
  font-size: var(--fs-base);
  font-weight: 600;
}

.prose dd {
  margin: 0 0 var(--s-4);
  font-size: var(--fs-base);
}

.prose li:last-child,
.prose dd:last-child,
.prose > :last-child {
  margin-bottom: 0;
}

/* Landing page ------------------------------------------------------------ */

.hero {
  display: grid;
  gap: var(--s-4);
  justify-items: start;
  margin-bottom: var(--s-6);
}

.hero .brand-mark {
  width: 2.5rem;
  height: 2.5rem;
}

.hero h1 {
  font-size: var(--fs-display);
}

.hero .lede {
  margin: 0;
  max-width: 34ch;
}

.hero .button {
  min-height: 3rem;
  padding: 0 var(--s-5);
}

.status {
  padding: var(--s-4);
  border-radius: var(--r-md);
  background: var(--sunken);
}

dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-2) var(--s-4);
  margin: 0;
}

dt {
  color: var(--fg-muted);
  font-size: var(--fs-sm);
}

dd {
  margin: 0;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* The state is a dot and a word, so it does not depend on telling green from
   red. app.js sets the attribute; the dot follows from it. */
dd[data-state] {
  display: flex;
  gap: var(--s-2);
  align-items: center;
}

dd[data-state]::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
}

dd[data-state="ok"] {
  color: var(--ok);
}

dd[data-state="error"] {
  color: var(--danger);
}

/* Responsive -------------------------------------------------------------- */

/* Out of phone territory: the cards can afford their full padding and the
   rows stop needing a line each. */
@media (min-width: 36rem) {
  .card {
    padding: var(--s-6);
  }

  .page-head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3) var(--s-4);
    align-items: center;
    justify-content: space-between;
  }

  .queue-item {
    grid-template-columns: 48px minmax(0, 1fr) auto;
  }

  .queue-action {
    grid-column: auto;
  }

  .transfer-actions button {
    flex: 0 0 auto;
    min-width: 0;
  }

  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  }

  .hero .button {
    padding: 0 var(--s-6);
  }
}

/* Tablet: the link row folds up into a badge, its URL and its buttons, with
   the detail line beneath, and the create-link options go side by side. */
@media (min-width: 48rem) {
  .topbar {
    position: sticky;
    top: 0;
    z-index: 10;
    box-shadow: var(--el-3);
  }

  .topnav {
    flex: 0 1 auto;
  }

  .topnav .who {
    display: block;
  }

  /*
    Named areas rather than auto-placement, because the detail line comes
    before the buttons in the markup: left to place itself, a line spanning
    every column pushes the buttons onto a third row of their own.
  */
  .link-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "badge url actions"
      "meta meta meta";
  }

  .link-row .badge {
    grid-area: badge;
  }

  .link-row input {
    grid-area: url;
  }

  .link-meta {
    grid-area: meta;
  }

  .row-actions {
    grid-area: actions;
  }

  .link-form {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* The two addresses go side by side; the message keeps a row of its own,
     since three lines of text beside a single-line field would be a column of
     mostly white space. */
  .email-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .email-note {
    grid-column: 1 / -1;
  }

  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  }

  .queue-item {
    grid-template-columns: 64px minmax(0, 1fr) auto;
  }

  .thumb {
    width: 64px;
    height: 64px;
  }
}

/* Desktop: the whole link row is one line, detail included. */
@media (min-width: 64rem) {
  :root {
    --gutter: var(--s-5);
  }

  .link-row {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "badge url meta actions";
  }

  .link-meta {
    white-space: nowrap;
  }
}

@media (min-width: 80rem) {
  :root {
    --shell-max: 68rem;
  }

  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  }
}

/* A reader who has asked for less movement gets none of it. The progress bars
   still fill: that is data arriving, not decoration. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  a.tile-preview:hover img,
  button:active,
  .button:active {
    transform: none;
  }
}
