/* surl — one stylesheet, no framework, no build step.
 *
 * A short link here is something printed on a poster or in a paper and read
 * back off it years later, so the interface is set like signage: a board of
 * cells divided by heavy black rules, flat colour fields, and the link token at
 * the size of a sign. The slug alphabet deliberately omits 0, 1, l and o so it
 * can be read off paper and typed back in; the token is set in a wide
 * monospace so that every character stands apart.
 *
 * One colour carries the identity: the turquoise field. The other colours are
 * signals and mean one thing each — yellow for "this just happened", orange for
 * "this no longer works". */

/* Two variable faces, latin subset, served from our own origin so the
   Content-Security-Policy stays at default-src 'self'. Licences sit beside
   them (archivo-OFL.txt, martian-mono-OFL.txt). */
@font-face {
  font-family: "Archivo";
  src: url("archivo.woff2") format("woff2");
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
}

@font-face {
  font-family: "Martian Mono";
  src: url("martian-mono.woff2") format("woff2");
  font-weight: 100 800;
  font-stretch: 75% 112.5%;
  font-display: swap;
}

:root {
  color-scheme: light dark;

  --paper:      light-dark(#ffffff, #101211);
  --muted:      light-dark(#eef1f0, #1b1f1e);
  --ink:        light-dark(#000000, #f2f4f3);
  --ink-soft:   light-dark(#3b4442, #bcc5c3);
  --ink-faint:  light-dark(#5b6664, #8f9a98);
  --link:       light-dark(#00665f, #5fd6cb);
  --field:      #2ec4b6;
  --field-soft: light-dark(#e3f7f4, #152f2c);
  --signal:     #ffd23f;
  --alarm:      #ff6b35;
  --danger:     light-dark(#c8102e, #ff7a85);

  --rule-w: 3px;
  --rule-heavy: var(--rule-w) solid var(--ink);
  --rule: 2px solid var(--ink);

  --sans: "Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Martian Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Perfect fourth from a 17px base. */
  --t-xs: 0.8125rem;
  --t-s:  0.9375rem;
  --t-m:  1.0625rem;
  --t-l:  1.4167rem;
  --t-xl: 1.889rem;
  --t-display: clamp(2.25rem, 1.5rem + 2.6vw, 3.5rem);

  /* Every band is full-bleed, but its content starts on the same left edge as
     the column below it. The gutter is that edge: half of whatever is left
     over once the column is laid out, and never less than --pad. */
  --pad: clamp(1rem, 3vw, 2rem);
  --inner: clamp(1.25rem, 3.5vw, 3rem);
  --wrap: 72rem;
  --gutter: max(var(--pad), (100vw - var(--wrap)) / 2);
}

/* A cell on a colour field is always read as light, whatever the reader's
   system prefers: black on turquoise in both themes. Setting color-scheme here
   makes every token inside resolve to its light value, so inputs, buttons and
   hints need no second set of rules. */
.on-field,
.on-alarm {
  color-scheme: light;
  color: var(--ink);
  --ink-soft: #102f2c;
  --ink-faint: #1c4540;
  --link: #000;
}
.on-field { background: var(--field); }
.on-alarm { background: var(--alarm); --ink-soft: #3a1606; --ink-faint: #4a1f0c; }

/* The opposite case: a black block that stays black in both themes. */
.on-ink {
  color-scheme: dark;
  background: #000;
  color: var(--ink);
}

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

/* The copy button and the no-script search button are hidden with the hidden
   attribute and revealed by script. An author rule giving a button a display
   would otherwise beat the browser's own [hidden] rule and show them anyway. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--t-m)/1.5 var(--sans);
  -webkit-text-size-adjust: 100%;
  font-synthesis: none;
}

main { flex: 1 0 auto; display: flex; flex-direction: column; }

/* A page that is nothing but its hero — sign-in, signed out, an error — lets
   the hero run down to the footer, so the board has no blank lower half. */
main > .split:last-child { flex: 1 0 auto; }

/* Stacked on a phone, the colour field takes that extra height and the paper
   cell under it stays as tall as its content. */
@media (max-width: 47.99rem) {
  main > .split:last-child { grid-template-rows: 1fr auto; }
}

a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

h1, h2, h3 { font-family: var(--sans); color: var(--ink); text-wrap: balance; }

/* Wide and heavy: sign lettering, used once per page for what the page is. */
.display {
  margin: 0 0 1.75rem;
  font-size: var(--t-display);
  font-weight: 800;
  font-stretch: 118%;
  line-height: 1;
  letter-spacing: -0.015em;
}

h2 {
  margin: 0 0 1.25rem;
  font-size: var(--t-xl);
  font-weight: 800;
  font-stretch: 112%;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

h3 {
  margin: 0 0 0.5rem;
  font-size: var(--t-l);
  font-weight: 700;
  font-stretch: 108%;
  line-height: 1.2;
}

.mono { font-family: var(--mono); }

.lede {
  margin: 0;
  max-width: 36ch;
  font-size: var(--t-l);
  line-height: 1.4;
}

.prose p { max-width: 62ch; }

/* --- the frame --------------------------------------------------------------
   Every page wears this: a masthead that says what the service is, the page's
   bands, and a colophon. Someone landing on a 410 from a link they were given
   six months ago should be able to tell what told them that.

   Rows of cells are drawn with the rule colour behind them and a gap between
   them, so the rules come from the layout itself and stay right however the
   row wraps. That only works if every line of cells is full, which is why
   each row below makes sure something always grows into the leftover space. */

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 10;
  padding: 0.5rem 0.75rem;
  background: var(--paper);
  border: var(--rule);
}

.masthead {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rule-w);
  background: var(--ink);
  border-bottom: var(--rule-heavy);
}

.identity, .tagline, .whoami, .session {
  display: flex;
  align-items: center;
  min-height: 4.75rem;
  margin: 0;
  padding: 0.9rem clamp(1rem, 2vw, 1.5rem);
  background: var(--paper);
}

/* The tagline takes the leftover width. The other cells grow by a token
   amount, so that if the tagline wraps onto a line of its own, the line it
   left behind is still filled rather than showing a slab of rule colour. */
.identity { flex: 0.001 0 auto; min-width: 0; }
.tagline  { flex: 1 1 14rem; font-size: var(--t-s); color: var(--ink-soft); }
.whoami   { flex: 0.001 0 auto; gap: 0.5rem; font-size: var(--t-s); justify-content: flex-end; }
.session  { flex: 0 0 auto; padding: 0 !important; }

.mark {
  font-size: var(--t-l);
  font-weight: 800;
  font-stretch: 125%;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
}
a.mark:hover { text-decoration: underline; text-decoration-thickness: 3px; }

/* The name, when a logo has taken its place in the mark cell: the same wide,
   heavy lettering, sized to sit on the tagline's baseline. */
.tagline-name {
  margin-right: 0.6em;
  font-size: var(--t-m);
  font-weight: 800;
  font-stretch: 125%;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* An institution's own wordmark, at whatever aspect ratio it has. Bounded so a
   large SVG cannot push the rest of the header around, and given an explicit
   height because an SVG carrying only a viewBox has no intrinsic size. */
.brand-logo {
  display: block;
  height: 2.25rem;
  max-width: 18rem;
  width: auto;
}

/* An institutional mark is usually one dark colour and vanishes on a dark
   background, so a second file can be supplied for that case. Swapped in CSS
   rather than srcset: a data: URI contains a comma, and srcset is
   comma-separated. */
.brand-logo.on-dark { display: none; }

@media (prefers-color-scheme: dark) {
  .brand-logo.on-light { display: none; }
  .brand-logo.on-dark { display: block; }
}

.session form { display: flex; height: 100%; margin: 0; }
.session a { display: flex; align-items: center; text-decoration: none; }

/* Signing out is a whole cell of the board, like the blueprint's menu items:
   a large target, and the one place the header answers to a pointer. The
   project site puts a link in the same cell. */
.session button,
.session a {
  height: 100%;
  min-height: 4.75rem;
  padding: 0 1.5rem;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-weight: 600;
}
.session button:hover, .session a:hover { background: var(--field); color: #000; }
.session button:focus-visible, .session a:focus-visible { outline-offset: -5px; }

@media (max-width: 40rem) {
  .identity, .tagline, .whoami, .session { min-height: 3.75rem; }
  .session button, .session a { min-height: 3.75rem; }
  .identity { flex-grow: 1; order: 1; }
  .session  { order: 2; }
  .tagline  { order: 3; flex-basis: 100%; }
  .whoami   { order: 4; flex-basis: 100%; justify-content: flex-start; }
}

.colophon {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rule-w);
  background: var(--ink);
  border-top: var(--rule-heavy);
  font-size: var(--t-xs);
  color: var(--ink-soft);
}

/* Separate cells, not one string joined by separators: the contact address,
   the legal pages, the licence and the build are unrelated facts and should
   not read as a sentence. */
.colophon > p {
  flex: 1 1 auto;
  margin: 0;
  padding: 1.1rem clamp(1rem, 2vw, 1.5rem);
  background: var(--paper);
}
.colophon > .help { flex-grow: 100; }
.colophon > .legal a + a { margin-inline-start: 1.25em; }

/* --- bands -------------------------------------------------------------------
   The page is a stack of full-width bands, each closed by a heavy rule. */

.band {
  margin: 0;
  padding: 0.9rem var(--gutter);
  border-bottom: var(--rule-heavy);
}

.wrap { padding-inline: var(--gutter); }
.section { padding-block: clamp(2.25rem, 4vw, 3.5rem); }

/* NAVIGATION — goes somewhere you have been or can return to. Always the first
   band under the masthead, never mixed in with the actions. */
.crumbs { padding-block: 0.65rem; font-size: var(--t-s); }

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}
.backlink::before {
  content: "‹";
  font-size: 1.4em;
  line-height: 0.8;
}
.backlink:hover { text-decoration: underline; text-underline-offset: 3px; }

/* FEEDBACK — what just happened, in the same words as the button that did it.
   A full band in a signal colour, and the one piece of unprompted motion in
   the interface, because it appears without being asked for and has to be
   noticed. */
.flash,
.result-note,
.notice-band {
  color-scheme: light;
  color: var(--ink);
  background: var(--signal);
  font-weight: 600;
  animation: flash-in 220ms ease-out;
}
.flash.warn,
.result.warn .result-note,
.notice-band.warn { background: var(--alarm); }
.notice-band { animation: none; }

@keyframes flash-in {
  from { opacity: 0; transform: translateY(-0.25rem); }
  to   { opacity: 1; transform: none; }
}

/* --- split hero --------------------------------------------------------------
   Two cells side by side, a colour field and a paper cell, stacked on a narrow
   screen. The outer edges of the pair line up with the column below. The
   result panel on a link's page is one of these too; it is named on its own
   because its class is what says what just happened to the link. */

.split,
.result {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--rule-w);
  background: var(--ink);
  border-bottom: var(--rule-heavy);
}

.cell {
  container-type: inline-size;
  min-width: 0;
  padding: clamp(2rem, 5vw, 4rem) var(--inner);
  background: var(--paper);
}
.cell.on-field { background: var(--field); }
.cell.on-alarm { background: var(--alarm); }
.cell.on-ink   { background: #000; }
.cell.start    { padding-inline-start: var(--gutter); }
.cell.end      { padding-inline-end: var(--gutter); }

@media (min-width: 48rem) {
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .split.solo { grid-template-columns: minmax(0, 1fr); }
  /* The link is the point of the page and the QR code a second form of it,
     so the link gets most of the width. */
  .result { grid-template-columns: minmax(0, 7fr) minmax(0, 3fr); }
}

@media (max-width: 47.99rem) {
  .split > .cell,
  .result > .cell { padding-inline: var(--pad); }
}

/* --- forms --------------------------------------------------------------- */

label {
  display: block;
  margin-bottom: 0.35rem;
  font-size: var(--t-s);
  font-weight: 600;
}

input[type="text"], input[type="url"], input[type="password"],
input[type="date"], input[type="search"], select, textarea {
  width: 100%;
  min-height: 3rem;
  margin: 0;
  padding: 0.65rem 0.85rem;
  font: 400 var(--t-m)/1.35 var(--sans);
  color: var(--ink);
  background: var(--paper);
  border: var(--rule);
  border-radius: 0;
}

::placeholder { color: var(--ink-faint); opacity: 1; }

textarea { min-height: 5.5rem; resize: vertical; }

input.mono { font-family: var(--mono); font-stretch: 87.5%; }

/* The one field that matters most on the create form, sized to match. */
.input-lg {
  min-height: 3.75rem;
  font-size: var(--t-l) !important;
  border-width: var(--rule-w) !important;
}

/* A drawn chevron rather than the platform's, in the rule colour so it follows
   the theme; an image here could not. Two half-squares make the point, sized in
   whole pixels so they meet exactly at any zoom rather than leaving a hairline. */
select {
  appearance: none;
  padding-right: 2.75rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink) 50%),
    linear-gradient(135deg, var(--ink) 50%, transparent 50%);
  background-position: calc(100% - 26px) 50%, calc(100% - 20px) 50%;
  background-size: 6px 6px;
  background-repeat: no-repeat;
  cursor: pointer;
}

.field { margin-bottom: 1.25rem; }
.field:last-child { margin-bottom: 0; }
.field .hint { margin: 0.4rem 0 0; font-size: var(--t-xs); color: var(--ink-faint); max-width: 60ch; }
.field .hint.bad { color: var(--danger); font-weight: 600; }
.field .hint.good { color: var(--ink); font-weight: 600; }

/* On the colour field red text would not be legible, so a complaint there is
   lifted onto paper. */
.on-field .hint.bad {
  display: inline-block;
  padding: 0.15rem 0.45rem;
  background: var(--paper);
}

/* A field is marked wrong only once it has been touched, so an untouched empty
   form is never covered in red.
   :user-invalid covers the browser's own constraint checking, including when
   scripting is unavailable. [data-touched] is set by the validation module and
   covers everything it reports through setCustomValidity. Either way the
   border follows the message shown underneath. */
input:user-invalid,
select:user-invalid,
textarea:user-invalid,
input[data-touched]:invalid,
select[data-touched]:invalid,
textarea[data-touched]:invalid {
  border-color: var(--danger);
}

.row { display: grid; gap: 1.25rem; margin-bottom: 1.25rem; }
.row .field { margin-bottom: 0; }
@media (min-width: 40rem) { .row.two { grid-template-columns: 1fr 1fr; } }

.check { display: flex; align-items: flex-start; gap: 0.5rem; }
.check input { width: auto; margin-top: 0.3rem; }
.check label { margin: 0; font-weight: 400; }

.form-narrow { max-width: 28rem; }
.form-wide { max-width: 46rem; }

/* ACTIONS — buttons do things. Black and square; hovering inverts them. */
button, .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 3rem;
  padding: 0.65rem 1.35rem;
  font: 600 var(--t-m)/1.2 var(--sans);
  color: var(--paper);
  background: var(--ink);
  border: var(--rule);
  border-radius: 0;
  cursor: pointer;
  text-decoration: none;
}
button:hover, .button:hover { color: var(--ink); background: var(--paper); }

/* Switched off — by a script, never by the markup, so that a page without
   scripting keeps a button that works. Faded rather than restyled, and it
   does not invert under the pointer, since nothing would happen. */
button:disabled, .button:disabled { opacity: 0.4; cursor: not-allowed; }
button:disabled:hover, .button:disabled:hover { color: var(--paper); background: var(--ink); }

button.ghost, .button.ghost {
  color: var(--ink);
  background: transparent;
}
button.ghost:hover, .button.ghost:hover {
  background: color-mix(in srgb, var(--ink) 9%, transparent);
}

button.small, .button.small {
  min-height: 2.25rem;
  padding: 0.3rem 0.7rem;
  font-size: var(--t-xs);
}

button.danger, .button.danger {
  color: var(--danger);
  background: transparent;
  border-color: var(--danger);
}
button.danger:hover, .button.danger:hover { color: var(--paper); background: var(--danger); }

/* Icons sit beside a word, never instead of one. Sized in em so they scale
   with the button they are in, and drawn in currentColor so a ghost button and
   a solid one each get the right one without a second rule. */
.icon {
  width: 1.1em;
  height: 1.1em;
  flex: none;
  /* The label's baseline is what the eye lines up on, not the icon's box. */
  margin-block: -0.15em;
}

/* Copying swaps the clipboard for a tick and turns the button yellow, so the
   confirmation is visible at a glance and not only in the word that changed. */
.icon-check { display: none; }
[data-copied] .icon-copy { display: none; }
[data-copied] .icon-check { display: block; }
button[data-copied] {
  color: #000;
  background: var(--signal);
  border-color: #000;
}

.actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: 1.5rem; }

.notice {
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  border: var(--rule);
  font-size: var(--t-s);
  font-weight: 600;
}
.notice.bad { color: var(--danger); border-color: var(--danger); background: var(--paper); }
.notice.warn { color-scheme: light; color: var(--ink); background: var(--alarm); }

/* --- the link token ---------------------------------------------------------
   The hero of the whole interface. The origin repeats on every row, so it is
   the slug that has to be legible at a glance: muting the host makes a list
   scannable without losing the fact that the whole thing is one address you
   can copy. */

.token {
  font-family: var(--mono);
  font-size: 1.125rem;
  font-weight: 600;
  font-stretch: 87.5%;
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.token .origin { color: var(--ink-faint); font-weight: 400; }
/* Where the name will go once the form is filled in: faint and light like the
   origin, and dashed under, so it reads as a gap rather than a name. */
.token .is-placeholder {
  color: var(--ink-faint);
  font-weight: 400;
  text-decoration: underline dashed;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.2em;
}

a.token { text-decoration: none; }
a.token:hover { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.2em; }

/* --- one line --------------------------------------------------------------
   At sign size a short link is one line, origin and name together, sized so
   the whole address fills the width of its cell and never breaks. The face is
   monospaced, so the width is exactly the length times one advance: 0.7em at
   this width, measured from the font, with a hair to spare. The length comes
   as a fit-N class, from fitClass in render.go or from preview.js as the
   preview is typed into. The floor keeps an address of some forty characters
   on one line even on a phone; only past it — a very long custom name on a
   narrow screen — may it wrap, which is better than running off the edge. */
.token--lg {
  display: inline-block;
  max-width: 100%;
  margin: 0;
  font-size: clamp(0.75rem, 100cqi / (var(--chars, 30) * 0.705), 4.5rem);
  font-weight: 700;
  font-stretch: 100%;
  line-height: 1.1;
}
a.token--lg:hover { text-decoration-thickness: 0.06em; }

.fit-12 { --chars: 12; } .fit-14 { --chars: 14; } .fit-16 { --chars: 16; } .fit-18 { --chars: 18; } .fit-20 { --chars: 20; }
.fit-22 { --chars: 22; } .fit-24 { --chars: 24; } .fit-26 { --chars: 26; } .fit-28 { --chars: 28; } .fit-30 { --chars: 30; }
.fit-32 { --chars: 32; } .fit-34 { --chars: 34; } .fit-36 { --chars: 36; } .fit-38 { --chars: 38; } .fit-40 { --chars: 40; }
.fit-42 { --chars: 42; } .fit-44 { --chars: 44; } .fit-46 { --chars: 46; } .fit-48 { --chars: 48; } .fit-50 { --chars: 50; }
.fit-52 { --chars: 52; } .fit-54 { --chars: 54; } .fit-56 { --chars: 56; } .fit-58 { --chars: 58; } .fit-60 { --chars: 60; }
.fit-62 { --chars: 62; } .fit-64 { --chars: 64; } .fit-66 { --chars: 66; } .fit-68 { --chars: 68; } .fit-70 { --chars: 70; }
.fit-72 { --chars: 72; } .fit-74 { --chars: 74; } .fit-76 { --chars: 76; } .fit-78 { --chars: 78; } .fit-80 { --chars: 80; }
.fit-82 { --chars: 82; } .fit-84 { --chars: 84; } .fit-86 { --chars: 86; } .fit-88 { --chars: 88; } .fit-90 { --chars: 90; }
.fit-92 { --chars: 92; } .fit-94 { --chars: 94; } .fit-96 { --chars: 96; } .fit-98 { --chars: 98; } .fit-100 { --chars: 100; }

.destination {
  margin: 0.2rem 0 0;
  color: var(--ink-soft);
  font-size: var(--t-s);
  /* anywhere, not break-all: a long URL wraps, but ordinary sentences in the
     same style do not get broken mid-word. */
  overflow-wrap: anywhere;
  max-width: 70ch;
}

/* --- create: the primary job, so it is the manage page's hero -------------
   Three numbered steps stacked full width, divided by heavy rules, each on
   its own ground: the settings on paper, the link they make on the field, and
   the one button on signal yellow — the colour of the "Created." that follows
   it. Numbered because they are a sequence, and full width because a long
   address is the thing being handled here and must not be cut short. */

.steps {
  display: grid;
  gap: var(--rule-w);
  background: var(--ink);
  border-bottom: var(--rule-heavy);
}

.step { padding-block: clamp(2rem, 4vw, 3.25rem); }

/* Both steps carry the same weight, so both titles are set alike: wide and
   heavy, with the number in a ring. The ring is the one round thing on the
   board, which is what lets it read as a marker rather than as structure. */
.step-title {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 0 0 1.75rem;
  font-size: clamp(1.75rem, 1.2rem + 1.8vw, 2.5rem);
  font-weight: 800;
  font-stretch: 118%;
  line-height: 1.05;
  letter-spacing: -0.01em;
}

.step-no {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.9em;
  height: 1.9em;
  border: 0.14em solid currentColor;
  border-radius: 50%;
  font-size: 0.55em;
  font-weight: 800;
  font-stretch: 100%;
  line-height: 1;
}

/* The first step's title is its field's label, and reads as a title. */
.step-title label {
  display: inline;
  margin: 0;
  font: inherit;
  cursor: pointer;
}

/* The optional settings, folded under one line. The marker is a small square
   in the rule weight, like every other box on the board, and turns from plus
   to minus as the section opens. */
details.more {
  margin-top: 1.5rem;
  border-top: var(--rule);
}
details.more > summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: fit-content;
  padding: 1rem 0;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before {
  content: "+";
  display: inline-grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border: var(--rule);
  font: 700 1.1rem/1 var(--mono);
}
details.more[open] > summary::before { content: "−"; }
details.more > summary:hover { text-decoration: underline; text-underline-offset: 3px; }
details.more[open] > summary { margin-bottom: 0.5rem; }

/* Custom name, expiry and its date on one row: the name gets the room, since
   what is typed there becomes the address. */
@media (min-width: 48rem) {
  .row.settings { grid-template-columns: minmax(0, 2fr) repeat(2, minmax(0, 1fr)); }
}

.preview-facts {
  display: grid;
  gap: 1rem 3rem;
  margin: 1.75rem 0 0;
}
@media (min-width: 48rem) {
  .preview-facts { grid-template-columns: minmax(0, 1fr) auto; }
}
.preview-facts dt { margin-bottom: 0.2rem; font-size: var(--t-xs); color: var(--ink-soft); }
.preview-facts dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.preview-facts dd.is-empty { font-weight: 400; color: var(--ink-faint); }

.step-go {
  color-scheme: light;
  color: var(--ink);
  background: var(--signal);
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
}

/* Sized like a step title, so the ring matches the other two. */
.go-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  font-size: clamp(1.75rem, 1.2rem + 1.8vw, 2.5rem);
}

/* The largest control on the page, because it is the one that matters. */
.button-go {
  min-height: 4rem;
  padding: 0.9rem 2.25rem;
  font-size: var(--t-l);
  font-weight: 700;
  font-stretch: 108%;
  border-width: var(--rule-w);
}

.go-note {
  flex: 1 1 20rem;
  max-width: 56ch;
  margin: 0;
  font-size: var(--t-s);
  line-height: 1.45;
}

/* --- the register ------------------------------------------------------------ */

/* The blueprint's filter box: one framed bar holding everything that changes
   what the list shows. */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  margin: 0 0 2rem;
  padding: clamp(0.75rem, 2vw, 1.25rem);
  border: var(--rule-heavy);
}
.toolbar form { display: flex; flex: 1 1 26rem; flex-wrap: wrap; gap: 0.75rem; margin: 0; }

.search-box { position: relative; flex: 1 1 14rem; }
.search-box input { padding-right: 3rem; }
.search-box .icon {
  position: absolute;
  top: 50%;
  right: 0.9rem;
  width: 1.35rem;
  height: 1.35rem;
  margin: 0;
  transform: translateY(-50%);
  pointer-events: none;
}

/* Narrow enough to sit beside the search box rather than under it. */
.sort { flex: 0 0 auto; width: auto; }

.scope-link { font-weight: 600; color: var(--ink); }

/* Present for screen readers, absent for everyone else: the select's purpose
   is obvious by sight and would only be noise as a visible label. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.tally {
  margin: 0 0 0.6rem;
  font-size: var(--t-s);
  color: var(--ink-soft);
}

.register { border: var(--rule-heavy); }

/* Each entry is a row of two cells: what the link is, and how much it is
   used. Visits get a cell of their own because two of the three orders sort
   by them, so the number has to be findable down the whole column. */
.entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  border-bottom: var(--rule);
}
.entry:last-child { border-bottom: 0; }
.entry:hover { background: var(--field-soft); }

.entry-body { min-width: 0; padding: 1.1rem 1.35rem; }

.entry-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
}

.entry .notes { margin: 0.35rem 0 0; font-weight: 500; max-width: 70ch; }

/* Withdrawn and expired links keep their place in the list, marked by an
   orange edge so they can be picked out down the whole column. */
.entry.is-retired { box-shadow: inset 0.5rem 0 0 var(--alarm); }
.entry.is-retired .entry-body { padding-left: calc(1.35rem + 0.5rem); }

.entry-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  min-width: 7.5rem;
  padding: 1.1rem 1.35rem;
  border-left: var(--rule);
}

.count {
  margin: 0;
  text-align: right;
  font-size: var(--t-xs);
  line-height: 1.2;
  color: var(--ink-soft);
}
.count .num {
  display: block;
  font-size: var(--t-xl);
  font-weight: 700;
  font-stretch: 112%;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--ink);
}

@media (max-width: 30rem) {
  /* The origin is the same on every row and would wrap on a phone; the name
     alone identifies the link. Hidden, not removed, so Copy still copies the
     whole address. */
  .entry .token .origin { display: none; }
  .entry-body, .entry-side { padding: 0.9rem 0.85rem; }
  .entry.is-retired .entry-body { padding-left: calc(0.85rem + 0.5rem); }
  .entry-side { min-width: 5rem; }
  .count .num { font-size: var(--t-l); }
}

/* Meta values sit in a spaced list rather than a run-on string, so each fact
   stays legible on a narrow screen. */
.meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin: 0.6rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-xs);
  color: var(--ink-faint);
}

/* A short status word attached to a thing, as a block of the warning colour. */
.badge, .state {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  color-scheme: light;
  color: var(--ink);
  background: var(--alarm);
  border: 2px solid #000;
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 1.4;
}
/* On an orange field the tag inverts, or it would vanish into it. */
.on-alarm .state { color: #fff; background: #000; }

.empty {
  margin: 0;
  padding: 2.5rem clamp(1rem, 3vw, 2rem);
  border: var(--rule-heavy);
  background: var(--muted);
  font-size: var(--t-l);
  color: var(--ink);
}

/* --- the result panel --------------------------------------------------------
   The short link and what just happened to it, in one place. A confirmation
   that floats at the top of the page while the thing it is about sits further
   down makes the reader do the joining up; here the message runs across the
   top of the same panel as the link.
   The field is turquoise while the link works and orange once it does not, so
   a withdrawn link never looks like a live one. The QR code takes the other
   cell: it is the same link in another form, and the thing people most often
   want next after copying it. */

.result-note {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0.9rem var(--gutter);
}

.state-line { margin: 0 0 1.25rem; }

.qr-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
}

.qr {
  width: min(15rem, 100%);
  aspect-ratio: 1;
  padding: 0.75rem;
  background: #fff;
  border: var(--rule-heavy);
}
.qr img { display: block; width: 100%; height: 100%; }

.qr-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.25rem;
  margin: 0;
  font-size: var(--t-s);
}

/* The one orchestrated moment: when a link has just been made, it prints onto
   the field from left to right, like a label coming out of a printer, and the
   code follows it. */
.result.good .token--lg { animation: print 720ms steps(16, end) both; }
.result.good .qr { animation: flash-in 300ms 600ms ease-out both; }

@keyframes print {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* --- link detail --------------------------------------------------------- */

/* Where the link goes and how it has been used: a row of cells directly under
   the result, so the facts about the link are part of the same board. */
.facts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--rule-w);
  margin: 0;
  background: var(--ink);
  border-bottom: var(--rule-heavy);
}

.fact {
  margin: 0;
  padding: 1.1rem var(--pad);
  background: var(--paper);
}
.fact dt { margin-bottom: 0.3rem; font-size: var(--t-xs); color: var(--ink-soft); }
.fact dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.fact dd.num {
  font-size: var(--t-xl);
  font-weight: 700;
  font-stretch: 112%;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.fact .by { display: block; font-weight: 400; font-size: var(--t-s); color: var(--ink-soft); }

@media (min-width: 36rem) {
  .facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .fact.wide { grid-column: 1 / -1; }
}

@media (min-width: 60rem) {
  /* The three short facts take what they need; the destination takes the rest. */
  .facts { grid-template-columns: minmax(0, 1fr) repeat(3, max-content); }
  .fact { padding-inline: 1.75rem 2.5rem; }
  .fact:not(.wide) dd { white-space: nowrap; }
  .fact.wide { grid-column: auto; }
  .fact:first-child { padding-inline-start: var(--gutter); }
  .fact:last-child { padding-inline-end: var(--gutter); }
}

.usage { margin-bottom: 3rem; }

/* Drawn as SVG with each bar's height as an attribute: the CSP allows no
   inline style, so heights set with style="" would be silently dropped. */
.sparkline {
  display: block;
  width: 100%;
  height: 7.5rem;
  margin: 0 0 0.25rem;
  padding: 0.85rem 0.85rem 0;
  background: var(--muted);
  border: var(--rule-heavy);
}
.sparkline rect { fill: var(--ink); }

/* A day with no visits is drawn as a faint baseline rather than nothing, so
   the shape of the month stays readable. */
.sparkline rect.empty { fill: var(--ink-faint); }

/* Where the things that cannot be taken back live: framed, and headed by a
   strip of the warning colour. */
.zone {
  max-width: 46rem;
  margin-top: 3.5rem;
  border: var(--rule-heavy);
}
.zone > h3 {
  margin: 0;
  padding: 0.8rem 1.25rem;
  color-scheme: light;
  color: var(--ink);
  background: var(--alarm);
  border-bottom: var(--rule-heavy);
}
.zone-body { padding: 1.25rem; }
.zone p { color: var(--ink-soft); font-size: var(--t-s); max-width: 62ch; }
.zone form { margin: 0; }

hr.rule { margin: 1.5rem 0; border: 0; border-top: var(--rule); }

/* --- sign-in and error pages --------------------------------------------- */

.status-code {
  margin: 2rem 0 0;
  font: 400 var(--t-s)/1 var(--mono);
  color: var(--ink-faint);
}

/* A withdrawn link is shown as the address it was, struck through: it is not
   gone, it is retired, and the name stays reserved. */
.token .gone { text-decoration: line-through; text-decoration-thickness: 0.07em; }

.cell-centred { display: flex; flex-direction: column; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
