:root {
  color-scheme: light;
  font-family: Inter, "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
  --ink: #181916;
  --paper: #f2efe6;
  --acid: #dfff00;
  --blue: #3548ff;
  --toolbar: rgba(22, 23, 20, 0.94);
  /* The desk the sheet of paper sits on. */
  --desk: #8b8b86;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; }

/*
 * iOS needs the prefixed property (unprefixed `user-select` only landed in
 * Safari 17.4) and its own callout switch. Without both, a long press on the
 * background runs a selection out across the whole page and pops the callout
 * menu. Editable text opts back in further down.
 */
html, body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

button, input, textarea { font: inherit; }

button { color: inherit; }

/*
 * The board is the desk: a fixed window onto the sheet. .world is the sheet
 * itself — one screen of paper, scaled as a whole by the camera, with the desk
 * showing around it once it is zoomed out or panned.
 */
.board {
  position: relative;
  width: 100vw;
  height: 100dvh;
  overflow: hidden;
  color: var(--ink);
  background-color: var(--desk);
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}

.world {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
  background-color: var(--paper);
  background-image:
    linear-gradient(rgba(21, 22, 19, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(21, 22, 19, 0.055) 1px, transparent 1px),
    radial-gradient(circle at 50% 45%, rgba(255,255,255,.84), rgba(223,218,205,.38));
  background-size: 28px 28px, 28px 28px, 100% 100%;
  /* Lifts the sheet off the desk so its edge is legible when zoomed out. */
  box-shadow: 0 0 0 1px rgba(21,22,19,.22), 0 10px 34px rgba(21,22,19,.28);
}

/* Dragging the board moves the camera, but only where that is allowed. */
.board.can-pan { cursor: grab; }
.board.is-panning { cursor: grabbing; }

/* Pinned to the window rather than the sheet, so the trim never pans away. */
.board::before {
  position: absolute;
  z-index: 6;
  top: 0;
  left: 0;
  width: 100%;
  height: 5px;
  pointer-events: none;
  content: "";
  background: linear-gradient(90deg, var(--acid) 0 32%, var(--blue) 32% 68%, #ff5a7a 68%);
}

#drawing-canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  touch-action: none;
}

.board.is-drawing #drawing-canvas {
  /*
   * Become the real topmost drawing surface. A transparent canvas left behind
   * the notes can still let iPad Chrome's native recognizer discover text even
   * when CSS pointer hit-testing says it should not.
   */
  z-index: 5;
  pointer-events: auto;
  cursor: crosshair;
}
.board.is-erasing #drawing-canvas { cursor: cell; }

/*
 * iPad Chrome can still start its native text-fragment selection from a fast
 * Pencil stroke when a selectable descendant sits underneath the canvas. Its
 * resulting callout contains "Create link". Lock the whole drawing world, not
 * only the note fields, so no descendant can opt back into that browser UI.
 */
.board.is-drawing .world,
.board.is-drawing .world * {
  -webkit-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}

/*
 * Draw mode freezes the notes. They stay exactly where they are and stay
 * readable, but pointers pass straight through to the board so a stroke
 * crossing a note is not interrupted, and nothing can be moved or edited.
 */
.notes-layer.is-frozen .note { pointer-events: none; cursor: default; }
.notes-layer.is-frozen .note__actions,
.notes-layer.is-frozen .note__resize { display: none; }
/*
 * pointer-events: none above should already keep a long press from landing on
 * the text, but that is a side effect of hit-testing, not a guarantee — it is
 * exactly the kind of thing a future edit to .note__title/.note__content could
 * quietly break. Selection and the iOS callout menu are overridden here
 * explicitly so a stroke drawn over a note can never be interrupted by text
 * getting selected underneath it, independent of how the browser resolves the
 * pointer-events cascade.
 */
.notes-layer.is-frozen .note__title,
.notes-layer.is-frozen .note__content {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;
  cursor: default;
}

.board-grain {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: .12;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
  mix-blend-mode: multiply;
}

.notes-layer { position: absolute; inset: 0; z-index: 4; pointer-events: none; }

/*
 * Every length inside a note is multiplied by --note-scale, so resizing is a
 * proportional zoom of the whole card rather than a reflow of its text. Height
 * is left to the content: a note is as tall as it needs to be to show all of
 * it, which is why nothing here scrolls.
 */
.note {
  --note-color: #f6e657;
  --note-scale: 1;
  position: absolute;
  display: flex;
  flex-direction: column;
  width: calc(278px * var(--note-scale));
  min-width: calc(220px * var(--note-scale));
  min-height: calc(178px * var(--note-scale));
  padding: calc(18px * var(--note-scale)) calc(18px * var(--note-scale)) calc(12px * var(--note-scale));
  overflow: hidden;
  overflow-wrap: break-word;
  background: var(--note-color);
  border: 1px solid rgba(0,0,0,.58);
  box-shadow: calc(9px * var(--note-scale)) calc(12px * var(--note-scale)) 0 rgba(28,28,24,.13), 0 calc(15px * var(--note-scale)) calc(38px * var(--note-scale)) rgba(28,28,24,.14);
  transform: rotate(var(--rotation, 0deg));
  transition: box-shadow 160ms ease, transform 160ms ease;
  pointer-events: auto;
  /* The whole card is the drag surface; the editable text and the grip opt out below. */
  cursor: grab;
  touch-action: none;
}

.note.is-dragging { cursor: grabbing; }
.note.is-resizing { cursor: nwse-resize; }

.note:hover,
.note.is-dragging,
.note.is-resizing,
.note:focus-within {
  z-index: 20 !important;
  box-shadow: calc(12px * var(--note-scale)) calc(16px * var(--note-scale)) 0 rgba(28,28,24,.15), 0 calc(22px * var(--note-scale)) calc(50px * var(--note-scale)) rgba(28,28,24,.2);
  transform: rotate(0deg) translateY(calc(-2px * var(--note-scale)));
}

.note--project { padding: 0; background: #171816; color: #fff; border-color: #0b0b0a; }
.note--project .note__handle { padding: calc(15px * var(--note-scale)) calc(16px * var(--note-scale)) 0; }
.note--project .note__title { padding: 0 calc(16px * var(--note-scale)); }
.note--project .note__content { padding: 0 calc(16px * var(--note-scale)) calc(16px * var(--note-scale)); }
.note--project .note__footer { padding: 0 calc(16px * var(--note-scale)) calc(12px * var(--note-scale)); }
.note--project .project-cover { display: block; width: calc(100% + 32px * var(--note-scale)); max-width: none; margin: calc(5px * var(--note-scale)) calc(-16px * var(--note-scale)) calc(14px * var(--note-scale)); border-block: 1px solid rgba(255,255,255,.22); pointer-events: none; }

.note__tape {
  position: absolute;
  top: calc(-8px * var(--note-scale));
  left: 50%;
  width: calc(70px * var(--note-scale));
  height: calc(22px * var(--note-scale));
  background: rgba(236,231,216,.64);
  border-inline: 1px dashed rgba(0,0,0,.12);
  transform: translateX(-50%) rotate(-2deg);
  backdrop-filter: blur(2px);
}

.note--project .note__tape { background: rgba(223,255,0,.8); }

.note__handle {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: calc(22px * var(--note-scale));
}

.note__eyebrow { font: 800 calc(.63rem * var(--note-scale))/1 ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; opacity: .65; }
.note__actions { display: flex; align-items: center; gap: calc(6px * var(--note-scale)); }
.note__actions button { display: grid; place-items: center; padding: 0; border: 0; background: transparent; cursor: pointer; }
.note__color { width: calc(13px * var(--note-scale)); height: calc(13px * var(--note-scale)); border: 1px solid currentColor !important; border-radius: 50%; }
.note__delete { width: calc(20px * var(--note-scale)); height: calc(20px * var(--note-scale)); font-size: calc(1.2rem * var(--note-scale)); line-height: 1; opacity: .65; }
.note__delete:hover { opacity: 1; }

.note__title {
  margin: calc(12px * var(--note-scale)) 0 calc(10px * var(--note-scale));
  outline: 0;
  font-size: calc(1.42rem * var(--note-scale));
  font-weight: 900;
  line-height: 1;
  letter-spacing: -.05em;
}

.note__title::after { content: " ↗"; font-size: .55em; vertical-align: top; opacity: .46; }
/*
 * `flex: 1 1 auto` rather than `flex: 1`: the note has no fixed height, so a
 * zero flex-basis would collapse the body to nothing. Growing from the natural
 * height instead keeps the footer pinned to the bottom of a min-height note.
 */
.note__content {
  flex: 1 1 auto;
  outline: 0;
  font-size: calc(.81rem * var(--note-scale));
  line-height: 1.68;
  -webkit-user-select: text;
  user-select: text;
  /* Links and addresses in the official notes keep their long-press menu. */
  -webkit-touch-callout: default;
}
/* The text is never a drag surface, so it keeps the caret and native gestures. */
.note__title,
.note__content { cursor: auto; touch-action: auto; }
/* Personal notes are typed in place, so their fields must stay selectable. */
.note__title[contenteditable="true"],
.note__content[contenteditable="true"] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
.note__content:empty::before, .note__title:empty::before { content: attr(data-placeholder); opacity: .38; }
.note__content p { margin: 0 0 .65em; }
.note__content strong { font-weight: 900; }
.note__content a { color: inherit; text-underline-offset: 3px; }
.project-link { display: block; text-decoration: none; cursor: pointer; }
.project-link .project-cover { transition: opacity .16s ease; }
.project-link:hover .project-cover { opacity: .82; }
.project-link p strong::after { content: " ↗"; }

.note__footer {
  display: flex;
  justify-content: space-between;
  align-items: end;
  min-height: calc(21px * var(--note-scale));
  margin-top: calc(8px * var(--note-scale));
  border-top: 1px solid rgba(0,0,0,.22);
  padding-top: calc(8px * var(--note-scale));
  font: 700 calc(.56rem * var(--note-scale))/1 ui-monospace, monospace;
  letter-spacing: .08em;
  opacity: .65;
}

.note--project .note__footer { border-color: rgba(255,255,255,.2); }

/* The corner mark stays decorative; .note__resize below is the actual control. */
.note__grip {
  margin-right: calc(-7px * var(--note-scale));
  margin-bottom: calc(-6px * var(--note-scale));
  font-size: calc(.8rem * var(--note-scale));
}

/*
 * The resize target, anchored to the card's bottom-right corner because that is
 * where people aim — it is where the browser's own resizer used to sit. Absolute
 * offsets resolve against the card's padding box, so pulling it out further than
 * any card's padding and letting `overflow: hidden` clip it guarantees the
 * corner is covered whatever the padding and scale happen to be.
 */
.note__resize {
  position: absolute;
  right: calc(-40px * var(--note-scale));
  bottom: calc(-40px * var(--note-scale));
  width: calc(72px * var(--note-scale));
  height: calc(72px * var(--note-scale));
  cursor: nwse-resize;
}

.toolbox {
  position: absolute;
  z-index: 50;
  left: 50%;
  bottom: 24px;
  width: min(720px, calc(100% - 32px));
  color: #f7f5ee;
  background: var(--toolbar);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 18px 44px rgba(0,0,0,.25);
  transform: translateX(-50%);
  backdrop-filter: blur(16px);
}

.toolbox__brand {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-bottom: 1px solid rgba(255,255,255,.12);
  font: 750 .53rem/1 ui-monospace, monospace;
  letter-spacing: .13em;
  opacity: .68;
}

#save-status { color: var(--acid); text-align: center; }
.draw-mode, #save-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.draw-mode { color: #fff; }
.locale-control { display: block; }
.locale-select {
  max-width: 116px;
  padding: 3px 22px 3px 7px;
  color: #f7f5ee;
  color-scheme: dark;
  background: rgba(255,255,255,.08);
  border: 1px solid rgba(255,255,255,.22);
  border-radius: 0;
  font: 700 .58rem/1.2 Inter, "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
  letter-spacing: .02em;
  cursor: pointer;
}
.locale-select option,
.locale-select optgroup {
  color: #f7f5ee;
  background-color: #161714;
}
.locale-select option:checked { color: #fff; background-color: #34362f; }
.locale-select:focus-visible { outline: 2px solid var(--acid); outline-offset: 2px; }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/*
 * Centred while there is slack, scrollable from the true start once there is
 * not. `justify-content: center` alone would push the first buttons past the
 * left edge where no amount of scrolling reaches them, and label lengths vary
 * a lot between the six languages.
 */
.toolbox__tools {
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  padding: 7px;
  overflow-x: auto;
  scrollbar-width: none;
}
.toolbox__tools::-webkit-scrollbar { display: none; }
.toolbox__tools > :first-child { margin-inline-start: auto; }
.toolbox__tools > :last-child { margin-inline-end: auto; }
/* Each group carries its own leading divider, so hiding the group takes the
   divider with it rather than leaving a rule against a rule. */
.toolbox__group { display: flex; align-items: stretch; }
.toolbox__rule { width: 1px; margin: 3px 7px; background: rgba(255,255,255,.17); }

.tool {
  min-width: 68px;
  padding: 8px 10px 6px;
  color: rgba(255,255,255,.64);
  background: transparent;
  border: 0;
  cursor: pointer;
}

.tool svg { display: block; width: 21px; height: 21px; margin: 0 auto 4px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: square; stroke-linejoin: miter; }
.tool span { display: block; font-size: .62rem; }
.tool:hover { color: #fff; background: rgba(255,255,255,.07); }
.tool:disabled { opacity: .3; cursor: not-allowed; }
.tool:disabled:hover { color: rgba(255,255,255,.64); background: transparent; }
.tool.is-active { color: var(--ink); background: var(--acid); }
.tool--accent { color: #fff; }
.tool--accent svg { stroke: var(--acid); }
.tool--toggle { color: #fff; }
.tool--toggle .tool__icon--eraser,
.tool--toggle.is-eraser .tool__icon--pen { display: none; }
.tool--toggle.is-eraser .tool__icon--eraser { display: block; }

.tool--mode { min-width: 84px; color: #fff; }
.tool--mode svg { stroke: var(--acid); }
.tool--mode .tool__icon--draw,
.tool--mode.is-draw .tool__icon--notes { display: none; }
.tool--mode.is-draw .tool__icon--draw { display: block; }
.tool--mode.is-draw { color: var(--ink); background: var(--acid); }
.tool--mode.is-draw svg { stroke: var(--ink); }
.tool--pencil.is-active { color: var(--ink); background: var(--acid); }
.tool--zoom { min-width: 44px; }
.tool--zoom-level { min-width: 58px; padding-top: 14px; }
.tool--zoom-level output { display: block; font: 700 .68rem/1 ui-monospace, monospace; letter-spacing: .02em; }
.tool--color { --paint-color: #23231f; }
.paint-swatch {
  display: block;
  width: 21px;
  height: 21px;
  margin: 0 auto 4px;
  border: 2px solid rgba(255,255,255,.8);
  border-radius: 50%;
  background: var(--paint-color);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.2);
}

.hsv-panel {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  width: min(320px, calc(100vw - 32px));
  padding: 17px;
  color: var(--ink);
  background: rgba(242,239,230,.98);
  border: 1px solid rgba(0,0,0,.36);
  box-shadow: 9px 10px 0 rgba(0,0,0,.18);
  transform: translateX(-50%);
}

.hsv-panel__header { display: grid; grid-template-columns: 24px 1fr auto; align-items: center; gap: 9px; margin-bottom: 15px; }
.hsv-panel__header strong { font-size: .76rem; }
.hsv-panel__header output { font: 700 .68rem/1 ui-monospace, monospace; letter-spacing: .05em; }
.hsv-preview { width: 24px; height: 24px; border: 1px solid rgba(0,0,0,.4); border-radius: 50%; }
.hsv-panel label { display: grid; grid-template-columns: 52px 16px 1fr; align-items: center; gap: 6px; font-size: .68rem; font-weight: 750; }
.hsv-panel label + label { margin-top: 11px; }
.hsv-panel label > span { color: rgba(24,25,22,.45); font: 700 .6rem/1 ui-monospace, monospace; }

.hsv-range {
  width: 100%;
  height: 9px;
  margin: 0;
  border-radius: 0;
  appearance: none;
  cursor: pointer;
}

.hsv-range--hue { background: linear-gradient(90deg, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
.hsv-range--saturation { background: linear-gradient(90deg, #fff, var(--hue-color)); }
.hsv-range--value { background: linear-gradient(90deg, #000, var(--value-color)); }
.hsv-range::-webkit-slider-thumb { width: 13px; height: 17px; border: 2px solid #fff; border-radius: 0; appearance: none; background: transparent; box-shadow: 0 0 0 1px #111; }
.hsv-range::-moz-range-thumb { width: 9px; height: 13px; border: 2px solid #fff; border-radius: 0; background: transparent; box-shadow: 0 0 0 1px #111; }
.hsv-range:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }

.board-hint {
  position: absolute;
  z-index: 5;
  right: 36px;
  bottom: 32px;
  padding-bottom: 7px;
  border-bottom: 1px solid rgba(0,0,0,.35);
  font: 700 .58rem/1 ui-monospace, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.coordinates {
  position: absolute;
  z-index: 5;
  top: 38px;
  right: 38px;
  display: flex;
  gap: 12px;
  font: 700 .55rem/1 ui-monospace, monospace;
  letter-spacing: .1em;
}

.coordinates span:first-child { padding: 4px 7px; color: var(--paper); background: var(--ink); }
.coordinates span:last-child { padding-top: 4px; }

.compliance {
  position: absolute;
  z-index: 5;
  left: 36px;
  bottom: 25px;
  max-width: 260px;
  color: rgba(24,25,22,.66);
  font: 650 .55rem/1.55 ui-monospace, monospace;
  letter-spacing: .035em;
}

.compliance p { margin: 0; }
.compliance p + p { margin-top: 3px; }
.compliance a { color: inherit; text-underline-offset: 2px; }
.compliance span { display: block; }
.beian-link { display: inline-flex; align-items: center; gap: 4px; }
.beian-link img { display: block; flex-shrink: 0; }

.consent-backdrop {
  position: fixed;
  z-index: 200;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(15, 16, 14, .58);
  backdrop-filter: blur(7px);
}

.consent-dialog {
  width: min(520px, 100%);
  /* Longer translations can outgrow a short phone. Bounding the dialog to the
     viewport keeps both buttons reachable instead of pushing them off-screen. */
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 28px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid rgba(0,0,0,.38);
  box-shadow: 12px 14px 0 rgba(0,0,0,.2);
}

.consent-dialog__eyebrow {
  margin: 0 0 14px;
  color: rgba(24,25,22,.58);
  font: 800 .62rem/1 ui-monospace, monospace;
  letter-spacing: .15em;
}

.consent-dialog h2 { margin: 0; font-size: clamp(1.7rem, 5vw, 2.35rem); line-height: 1; letter-spacing: -.055em; }
.consent-dialog__copy { margin-top: 20px; font-size: .88rem; line-height: 1.75; }
.consent-dialog__copy p { margin: 0; }
.consent-dialog__copy p + p { margin-top: 10px; color: rgba(24,25,22,.64); }
.consent-dialog__copy .consent-dialog__legal {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgba(24,25,22,.16);
  color: rgba(24,25,22,.48);
  font-size: .7rem;
  line-height: 1.65;
}
.consent-dialog__copy ul { margin: 14px 0 0; padding-left: 1.15rem; }
.consent-dialog__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 25px; }

.consent-button {
  min-height: 42px;
  padding: 10px 16px;
  border: 1px solid var(--ink);
  font-size: .78rem;
  font-weight: 800;
  cursor: pointer;
}

.consent-button--quiet { color: var(--ink); background: transparent; }
.consent-button--accept { color: var(--paper); background: var(--ink); }
.consent-button:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }

@media (max-width: 900px) {
  .coordinates { top: 28px; right: 20px; }
  .coordinates span:last-child { display: none; }
  .board-hint { display: none; }
  .toolbox { bottom: 12px; }
}

/*
 * The page itself never scrolls on any device now: the camera pans the world
 * instead, so the toolbar stays put and the board is a fixed window.
 */
@media (any-pointer: coarse) and (min-width: 641px) and (max-width: 1366px) {
  .compliance { bottom: 100px; }
}

@media (max-width: 640px) {
  .note { min-width: calc(190px * var(--note-scale)); }
  .toolbox { position: fixed; }
  .toolbox__brand { grid-template-columns: minmax(0, 1fr) auto; }
  #save-status { display: none; }
  .locale-select { max-width: 106px; }
  .tool { min-width: 59px; padding-inline: 7px; }
  .toolbox__rule { margin-inline: 3px; }
  .compliance { left: 20px; bottom: 100px; max-width: calc(100% - 40px); }
  .consent-backdrop { padding: 16px; }
  .consent-dialog { max-height: calc(100dvh - 32px); padding: 23px 20px; }
  .consent-dialog__actions { flex-direction: column-reverse; }
  .consent-button { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; }
}
