gyojeong 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,19 +60,21 @@ import { hostname, homedir as homedir2 } from "node:os";
60
60
  import path2 from "node:path";
61
61
 
62
62
  // ../feedback-core/dist/index.js
63
- var validateSelection = (text, selection) => {
63
+ var createSelectionValidator = (text) => {
64
64
  const normalizedText = text.normalize("NFC");
65
- const normalizedSelectedText = selection.selected_text.normalize("NFC");
66
65
  const textCodePoints = Array.from(normalizedText);
67
- if (!Number.isFinite(selection.start) || !Number.isFinite(selection.end) || !Number.isInteger(selection.start) || !Number.isInteger(selection.end) || selection.start < 0 || selection.start >= selection.end || selection.end > textCodePoints.length) {
68
- return { ok: false, reason: "stale_selection" };
69
- }
70
- const selectedSlice = textCodePoints.slice(selection.start, selection.end).join("");
71
- if (selectedSlice !== normalizedSelectedText) {
72
- return { ok: false, reason: "stale_selection" };
73
- }
74
- return { ok: true, normalized_text: normalizedText };
66
+ return (selection) => {
67
+ if (!Number.isFinite(selection.start) || !Number.isFinite(selection.end) || !Number.isInteger(selection.start) || !Number.isInteger(selection.end) || selection.start < 0 || selection.start >= selection.end || selection.end > textCodePoints.length) {
68
+ return { ok: false, reason: "stale_selection" };
69
+ }
70
+ const selectedSlice = textCodePoints.slice(selection.start, selection.end).join("");
71
+ if (selectedSlice !== selection.selected_text.normalize("NFC")) {
72
+ return { ok: false, reason: "stale_selection" };
73
+ }
74
+ return { ok: true, normalized_text: normalizedText };
75
+ };
75
76
  };
77
+ var validateSelection = (text, selection) => createSelectionValidator(text)(selection);
76
78
 
77
79
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
78
80
  var _a;
@@ -1046,7 +1048,6 @@ var string = (params) => {
1046
1048
  };
1047
1049
  var integer = /^-?\d+$/;
1048
1050
  var number = /^-?\d+(?:\.\d+)?$/;
1049
- var boolean = /^(?:true|false)$/i;
1050
1051
  var lowercase = /^[^A-Z]*$/;
1051
1052
  var uppercase = /^[^a-z]*$/;
1052
1053
 
@@ -1929,27 +1930,6 @@ var $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst, d
1929
1930
  $ZodCheckNumberFormat.init(inst, def);
1930
1931
  $ZodNumber.init(inst, def);
1931
1932
  });
1932
- var $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
1933
- $ZodType.init(inst, def);
1934
- inst._zod.pattern = boolean;
1935
- inst._zod.parse = (payload, _ctx) => {
1936
- if (def.coerce)
1937
- try {
1938
- payload.value = Boolean(payload.value);
1939
- } catch (_) {
1940
- }
1941
- const input = payload.value;
1942
- if (typeof input === "boolean")
1943
- return payload;
1944
- payload.issues.push({
1945
- expected: "boolean",
1946
- code: "invalid_type",
1947
- input,
1948
- inst
1949
- });
1950
- return payload;
1951
- };
1952
- });
1953
1933
  var $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
1954
1934
  $ZodType.init(inst, def);
1955
1935
  inst._zod.parse = (payload) => payload;
@@ -2565,28 +2545,6 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
2565
2545
  return payload;
2566
2546
  };
2567
2547
  });
2568
- var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
2569
- $ZodType.init(inst, def);
2570
- if (def.values.length === 0) {
2571
- throw new Error("Cannot create literal schema with no valid values");
2572
- }
2573
- const values = new Set(def.values);
2574
- inst._zod.values = values;
2575
- inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
2576
- inst._zod.parse = (payload, _ctx) => {
2577
- const input = payload.value;
2578
- if (values.has(input)) {
2579
- return payload;
2580
- }
2581
- payload.issues.push({
2582
- code: "invalid_value",
2583
- values: def.values,
2584
- input,
2585
- inst
2586
- });
2587
- return payload;
2588
- };
2589
- });
2590
2548
  var $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
2591
2549
  $ZodType.init(inst, def);
2592
2550
  inst._zod.optin = "optional";
@@ -3307,13 +3265,6 @@ function _int(Class2, params) {
3307
3265
  });
3308
3266
  }
3309
3267
  // @__NO_SIDE_EFFECTS__
3310
- function _boolean(Class2, params) {
3311
- return new Class2({
3312
- type: "boolean",
3313
- ...normalizeParams(params)
3314
- });
3315
- }
3316
- // @__NO_SIDE_EFFECTS__
3317
3268
  function _unknown(Class2) {
3318
3269
  return new Class2({
3319
3270
  type: "unknown"
@@ -3959,9 +3910,6 @@ var numberProcessor = (schema, ctx, _json, _params) => {
3959
3910
  if (typeof multipleOf === "number")
3960
3911
  json.multipleOf = multipleOf;
3961
3912
  };
3962
- var booleanProcessor = (_schema, _ctx, json, _params) => {
3963
- json.type = "boolean";
3964
- };
3965
3913
  var neverProcessor = (_schema, _ctx, json, _params) => {
3966
3914
  json.not = {};
3967
3915
  };
@@ -3976,46 +3924,6 @@ var enumProcessor = (schema, _ctx, json, _params) => {
3976
3924
  json.type = "string";
3977
3925
  json.enum = values;
3978
3926
  };
3979
- var literalProcessor = (schema, ctx, json, _params) => {
3980
- const def = schema._zod.def;
3981
- const vals = [];
3982
- for (const val of def.values) {
3983
- if (val === void 0) {
3984
- if (ctx.unrepresentable === "throw") {
3985
- throw new Error("Literal `undefined` cannot be represented in JSON Schema");
3986
- } else {
3987
- }
3988
- } else if (typeof val === "bigint") {
3989
- if (ctx.unrepresentable === "throw") {
3990
- throw new Error("BigInt literals cannot be represented in JSON Schema");
3991
- } else {
3992
- vals.push(Number(val));
3993
- }
3994
- } else {
3995
- vals.push(val);
3996
- }
3997
- }
3998
- if (vals.length === 0) {
3999
- } else if (vals.length === 1) {
4000
- const val = vals[0];
4001
- json.type = val === null ? "null" : typeof val;
4002
- if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
4003
- json.enum = [val];
4004
- } else {
4005
- json.const = val;
4006
- }
4007
- } else {
4008
- if (vals.every((v) => typeof v === "number"))
4009
- json.type = "number";
4010
- if (vals.every((v) => typeof v === "string"))
4011
- json.type = "string";
4012
- if (vals.every((v) => typeof v === "boolean"))
4013
- json.type = "boolean";
4014
- if (vals.every((v) => v === null))
4015
- json.type = "null";
4016
- json.enum = vals;
4017
- }
4018
- };
4019
3927
  var customProcessor = (_schema, ctx, _json, _params) => {
4020
3928
  if (ctx.unrepresentable === "throw") {
4021
3929
  throw new Error("Custom types cannot be represented in JSON Schema");
@@ -4720,14 +4628,6 @@ var ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, def
4720
4628
  function int(params) {
4721
4629
  return _int(ZodNumberFormat, params);
4722
4630
  }
4723
- var ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
4724
- $ZodBoolean.init(inst, def);
4725
- ZodType.init(inst, def);
4726
- inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
4727
- });
4728
- function boolean2(params) {
4729
- return _boolean(ZodBoolean, params);
4730
- }
4731
4631
  var ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
4732
4632
  $ZodUnknown.init(inst, def);
4733
4633
  ZodType.init(inst, def);
@@ -4918,27 +4818,6 @@ function _enum(values, params) {
4918
4818
  ...util_exports.normalizeParams(params)
4919
4819
  });
4920
4820
  }
4921
- var ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
4922
- $ZodLiteral.init(inst, def);
4923
- ZodType.init(inst, def);
4924
- inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params);
4925
- inst.values = new Set(def.values);
4926
- Object.defineProperty(inst, "value", {
4927
- get() {
4928
- if (def.values.length > 1) {
4929
- throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
4930
- }
4931
- return def.values[0];
4932
- }
4933
- });
4934
- });
4935
- function literal(value, params) {
4936
- return new ZodLiteral({
4937
- type: "literal",
4938
- values: Array.isArray(value) ? value : [value],
4939
- ...util_exports.normalizeParams(params)
4940
- });
4941
- }
4942
4821
  var ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
4943
4822
  $ZodTransform.init(inst, def);
4944
4823
  ZodType.init(inst, def);
@@ -5135,7 +5014,6 @@ var BoundedCorrectionSchema = trimmedNfcBoundedText(4e3, "correction").refine((v
5135
5014
  });
5136
5015
  var IdempotencyKeySchema = string2().trim().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/);
5137
5016
  var IdentifierSchema = uuid2();
5138
- var DomainSchema = string2().trim().min(1).max(64).regex(/^[a-z][a-z0-9_-]*$/);
5139
5017
  var IsoDateTimeSchema = iso_exports.datetime();
5140
5018
  var ReviewAttachmentFilenameSchema = nfcBoundedText(255, "filename").refine((value) => value.length > 0, { message: "filename must not be empty" }).refine((value) => value !== "." && value !== "..", { message: "filename must be a basename" }).refine((value) => !/[\/\\\0]/u.test(value), { message: "filename must be a basename" });
5141
5019
  var ReviewAttachmentMediaTypeSchema = _enum(["image/png", "image/jpeg", "image/webp"]);
@@ -5235,21 +5113,6 @@ var LanguageTagSchema = string2().transform((value, context) => {
5235
5113
  return NEVER;
5236
5114
  }
5237
5115
  });
5238
- var OperationSchema = _enum(["translate", "rewrite", "post_edit", "review"]);
5239
- var SupportLevelSchema = _enum(["verified", "experimental", "unsupported"]);
5240
- var RouteSchema = object({
5241
- operation: OperationSchema,
5242
- source_language: LanguageTagSchema,
5243
- target_language: LanguageTagSchema,
5244
- domain: DomainSchema,
5245
- support_level: SupportLevelSchema
5246
- }).strict();
5247
- var StructuredErrorSchema = object({
5248
- code: string2().min(1).max(80),
5249
- retryable: boolean2(),
5250
- message: string2().min(1).max(1e3),
5251
- next_action: string2().min(1).max(1e3)
5252
- }).strict();
5253
5116
  var MAX_SURFACE_SELECTOR_CODE_POINTS = 500;
5254
5117
  var MAX_SURFACE_TILES = 32;
5255
5118
  var MAX_SURFACE_TILE_BYTES = 2 * 1024 * 1024;
@@ -5304,20 +5167,6 @@ var SurfaceSnapshotInputSchema = object({
5304
5167
  tiles: array(SurfaceTileInputSchema).min(1).max(MAX_SURFACE_TILES),
5305
5168
  units: array(SurfaceUnitSchema).min(1).max(MAX_SURFACE_UNITS)
5306
5169
  }).strict();
5307
- var SurfaceTileManifestSchema = object({
5308
- index: int().nonnegative(),
5309
- top: int().nonnegative(),
5310
- height: int().positive(),
5311
- media_type: _enum(["image/png", "image/jpeg", "image/webp"]),
5312
- size_bytes: int().positive()
5313
- }).strict();
5314
- var SurfaceSnapshotManifestSchema = object({
5315
- page_width: int().positive(),
5316
- page_height: int().positive(),
5317
- captured_at: IsoDateTimeSchema,
5318
- tiles: array(SurfaceTileManifestSchema).min(1).max(MAX_SURFACE_TILES),
5319
- units: array(SurfaceUnitSchema).min(1).max(MAX_SURFACE_UNITS)
5320
- }).strict();
5321
5170
  var SourceSurfaceSchema = object({
5322
5171
  target_url: url().refine((value) => ["http:", "https:"].includes(new URL(value).protocol), {
5323
5172
  message: "target_url must be an http(s) URL"
@@ -5334,9 +5183,7 @@ var TranslationPublishInputSchema = object({
5334
5183
  run_id: IdentifierSchema,
5335
5184
  source_text: NfcBoundedTextSchema,
5336
5185
  jobs: array(TranslationPublishJobSchema).length(1),
5337
- attachments: array(unknown()).max(MAX_REVIEW_ATTACHMENTS).pipe(array(ReviewAttachmentInputSchema)).optional(),
5338
- upload_consent: literal(true),
5339
- idempotency_key: IdempotencyKeySchema
5186
+ attachments: array(unknown()).max(MAX_REVIEW_ATTACHMENTS).pipe(array(ReviewAttachmentInputSchema)).optional()
5340
5187
  }).strict().superRefine((value, context) => {
5341
5188
  const filenames = /* @__PURE__ */ new Set();
5342
5189
  let totalSize = 0;
@@ -5551,20 +5398,6 @@ var readAttachment = (home, id, attachmentId) => {
5551
5398
  }
5552
5399
  return { media_type: manifest.media_type, bytes: Buffer.from(readFileSync2(file, "utf8"), "base64") };
5553
5400
  };
5554
- var readRun = (home, id) => {
5555
- const dir = runDir(home, id);
5556
- assertRunExists(dir);
5557
- const content = readJson(path2.join(dir, "content.json"), null);
5558
- const feedback = readJson(path2.join(dir, "feedback.json"), []);
5559
- const state = readJson(path2.join(dir, "state.json"), { processed_feedback_ids: [] });
5560
- const processed = new Set(state.processed_feedback_ids ?? []);
5561
- return {
5562
- content,
5563
- feedback,
5564
- processed_feedback_ids: [...processed],
5565
- pending_feedback_ids: feedback.map((item) => item.id).filter((fid) => !processed.has(fid))
5566
- };
5567
- };
5568
5401
  var appendFeedback = (home, id, rawItems) => {
5569
5402
  if (!Array.isArray(rawItems) || rawItems.length === 0 || rawItems.length > 100) {
5570
5403
  throw problem(400, "invalid_request", "\uD53C\uB4DC\uBC31\uC740 \uD55C \uBC88\uC5D0 1~100\uAC74\uAE4C\uC9C0 \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.", "\uAC80\uC218\uD568 \uB0B4\uC6A9\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
@@ -5595,6 +5428,8 @@ var appendFeedback = (home, id, rawItems) => {
5595
5428
  category: item.category,
5596
5429
  scope: item.scope,
5597
5430
  selection: item.selection,
5431
+ // Offsets refer to this draft; `revise` keeps it so a later round can still verify the item.
5432
+ draft_version: content.draft_version ?? 0,
5598
5433
  ...item.term ? { term: item.term } : {},
5599
5434
  ...item.correction ? { correction: item.correction } : {},
5600
5435
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5625,15 +5460,23 @@ var listRuns = (home, now = Date.now()) => {
5625
5460
  if (!existsSync2(runsDir(home))) return [];
5626
5461
  return readdirSync(runsDir(home)).filter((name) => REVIEW_ID.test(name)).flatMap((id) => {
5627
5462
  try {
5628
- const run = readRun(home, id);
5629
- const createdAt = run.content.created_at;
5463
+ const dir = runDir(home, id);
5464
+ let meta2 = null;
5465
+ try {
5466
+ meta2 = readJson(path2.join(dir, "meta.json"), null);
5467
+ } catch {
5468
+ meta2 = null;
5469
+ }
5470
+ const createdAt = (meta2?.created_at ? meta2 : readJson(path2.join(dir, "content.json"), null)).created_at;
5471
+ const feedback = readJson(path2.join(dir, "feedback.json"), []);
5472
+ const processed = new Set(readJson(path2.join(dir, "state.json"), { processed_feedback_ids: [] }).processed_feedback_ids ?? []);
5630
5473
  return [
5631
5474
  {
5632
5475
  review_id: id,
5633
5476
  created_at: createdAt,
5634
5477
  age_days: Math.floor((now - Date.parse(createdAt)) / 864e5),
5635
- feedback: run.feedback.length,
5636
- pending: run.pending_feedback_ids.length
5478
+ feedback: feedback.length,
5479
+ pending: feedback.filter((item) => !processed.has(item.id)).length
5637
5480
  }
5638
5481
  ];
5639
5482
  } catch {
@@ -5727,7 +5570,7 @@ var allowedOrigin = (request, allowedOrigins, allowedHosts) => {
5727
5570
  return false;
5728
5571
  }
5729
5572
  };
5730
- var handleApi = async (request, response, pathname, context) => {
5573
+ var handleApi = async (request, response, pathname, context, query = new URLSearchParams()) => {
5731
5574
  if (pathname === "/api/health" && request.method === "GET") {
5732
5575
  sendJson(response, 200, { ready: true, storage: "local", home_id: homeId(context.home) });
5733
5576
  return true;
@@ -5735,26 +5578,15 @@ var handleApi = async (request, response, pathname, context) => {
5735
5578
  const reviewMatch = pathname.match(new RegExp(`^/api/reviews/${REVIEW_PATH}$`, "u"));
5736
5579
  if (reviewMatch && request.method === "GET") {
5737
5580
  try {
5738
- const { check: _check2, ...content } = readContent(context.home, reviewMatch[1]);
5739
- sendJson(response, 200, { ...content, attachments: (content.attachments ?? []).map(({ file: _file2, ...rest }) => rest) });
5740
- } catch (error2) {
5741
- const known = knownProblem(error2, "\uAC80\uC218 \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
5742
- sendJson(response, known.status, known.body);
5743
- }
5744
- return true;
5745
- }
5746
- const feedbackMatch = pathname.match(new RegExp(`^/api/reviews/${REVIEW_PATH}/feedback$`, "u"));
5747
- if (feedbackMatch && request.method === "GET") {
5748
- try {
5749
- const run = readRun(context.home, feedbackMatch[1]);
5581
+ const { check: _check2, source_surface: surface, ...content } = readContent(context.home, reviewMatch[1]);
5582
+ const withSurface = query.get("surface") === "1" && surface ? { source_surface: surface } : {};
5750
5583
  sendJson(response, 200, {
5751
- review_id: feedbackMatch[1],
5752
- feedback: run.feedback,
5753
- processed_feedback_ids: run.processed_feedback_ids,
5754
- pending_feedback_ids: run.pending_feedback_ids
5584
+ ...content,
5585
+ ...withSurface,
5586
+ attachments: (content.attachments ?? []).map(({ file: _file2, ...rest }) => rest)
5755
5587
  });
5756
5588
  } catch (error2) {
5757
- const known = knownProblem(error2, "\uD53C\uB4DC\uBC31\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
5589
+ const known = knownProblem(error2, "\uAC80\uC218 \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
5758
5590
  sendJson(response, known.status, known.body);
5759
5591
  }
5760
5592
  return true;
@@ -5925,7 +5757,7 @@ var startReviewServer = async ({ host, port, root, home = storeHome(), idle = lo
5925
5757
  return;
5926
5758
  }
5927
5759
  const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
5928
- if (await handleApi(request, response, url2.pathname, context)) return;
5760
+ if (await handleApi(request, response, url2.pathname, context, url2.searchParams)) return;
5929
5761
  if (request.method !== "GET" && request.method !== "HEAD") {
5930
5762
  response.writeHead(405).end();
5931
5763
  return;
@@ -1,7 +0,0 @@
1
- /*
2
- Referrer-Policy: no-referrer
3
- Cache-Control: no-store
4
- X-Content-Type-Options: nosniff
5
- X-Frame-Options: DENY
6
- Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
7
- Content-Security-Policy: default-src 'self'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'none'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'
@@ -1 +0,0 @@
1
- (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=()=>{if(typeof crypto<`u`&&typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=(e[6]??0)&15|64,e[8]=(e[8]??0)&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`},t=(e,t)=>{let n=e.body;return[n?.message??(e instanceof Error?e.message:t),n?.next_action].filter(Boolean).join(` `)},n=[`terminology`,`meaning_shift`],r={level:`experimental`,correction_required:n,correction_optional:[`unspecified`,`awkward_expression`,`omission`,`addition`,`register`],correction_forbidden:[`deletion`],feedback_categories:[`unspecified`,`deletion`,...n,`awkward_expression`,`omission`,`addition`,`register`],feedback_scopes:[`run_only`,`personal`]},i=(e,t,n,r=!1)=>({code:e,message:t,next_action:n,retryable:r}),a=(e,t,n,r,a=!1)=>Object.assign(Error(t),{status:r,body:i(e,t,n,a)}),o=async e=>{let t;try{t=await e.json()}catch{t=void 0}throw Object.assign(Error(t?.message??`로컬 검수 서버 요청에 실패했습니다.`),{status:e.status,body:t})},s=async(e,t)=>{try{let n=await fetch(e,{cache:`no-store`,...t});return n.ok||await o(n),n}catch(e){throw e.body?e:a(`local_server_unavailable`,`로컬 검수 서버에 연결할 수 없습니다.`,`에이전트에게 검수 링크를 다시 요청해 주세요. 서버가 쉬고 있으면 링크를 만들 때 다시 켜집니다.`,503,!0)}},c=async e=>{let t=await(await s(`/api/reviews/${encodeURIComponent(e)}`)).json();return{run:t.run,job:t.job,source_text:t.source_text,draft_text:t.draft_text,attachments:(t.attachments??[]).map(({file:e,...t})=>t),surface:t.source_surface??null,support:r}},l=async(e,t)=>(await s(`/api/reviews/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}`)).blob(),u=async(e,t)=>{if(t.length===0)return[];let n=await s(`/api/review-feedback`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({review_id:e,items:t})}),r;try{r=await n.json()}catch{r=void 0}if(!n.ok){let e=r;throw Object.assign(Error(e?.message??`피드백 저장에 실패했습니다.`),{status:n.status,body:e})}let i=r?.results;if(!Array.isArray(i)||i.length!==t.length)throw a(`invalid_server_response`,`로컬 검수 서버 응답이 올바르지 않습니다.`,`서버를 다시 실행해 주세요.`,502);return i},d=async(e,t=!1)=>{await s(`/api/review-finalize`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({review_id:e,force:t})})},f=120,p={unspecified:`?`,deletion:`삭제하기`,awkward_expression:`어색한 표현`,terminology:`용어`,omission:`누락`,addition:`추가`,meaning_shift:`의미 변화`,register:`문체`},m=new Set([`terminology`,`meaning_shift`]),h=new Set([`terminology`]),g=`용어 기준 후보로 남깁니다.`,_=e=>{if(e.onceOnly)return{scope:`run_only`};if(!h.has(e.category))return{scope:`run_only`,blocked:`category`};let t=(e.term||e.selectedText).normalize(`NFC`);return Array.from(t).length>f?{scope:`run_only`,blocked:`no_term`}:{scope:`personal`}},v=e=>e===`category`?`이 유형은 이번 문서에만 적용됩니다.`:`문장이 길어 이번 문서에만 적용됩니다. 단어를 지정하면 용어 기준 후보가 됩니다.`;export{v as a,d as c,u as d,_ as i,e as l,g as n,c as o,p as r,l as s,m as t,t as u};
@@ -1,3 +0,0 @@
1
- import{a as e,d as t,i as n,n as r,o as i,r as a,t as o,u as s}from"./feedback-contract-BsrsBZh-.js";var c=new URLSearchParams(location.search),l=new URLSearchParams(location.hash.startsWith(`#`)?location.hash.slice(1):location.hash).get(`review`),u=!!l,d=null,ee=c.get(`target`),f=document.getElementById(`source`),p=null,m=null,h=``,g=document.getElementById(`reviewMode`),_=document.getElementById(`basket`),v=document.getElementById(`items`),te=document.getElementById(`count`),y=document.getElementById(`sheet`),ne=document.getElementById(`selection`),b=document.getElementById(`correction`),re=document.getElementById(`status`),ie=document.getElementById(`barMode`),x=document.getElementById(`add`),S=document.getElementById(`copyFeedback`),C=document.getElementById(`remember`),w=`gyojeong:${l||`local`}:pending`,T=c.get(`selectors`)||`h1,h2,h3,h4,h5,h6,p,li,td,th,blockquote,figcaption,[data-review-text]`,E=document.getElementById(`surface`),D=document.getElementById(`categories`),O=document.getElementById(`scope`),ae=document.getElementById(`termLabel`),k=document.getElementById(`term`),A=document.getElementById(`correctionLabel`),oe=document.getElementById(`rememberLabel`),se=document.getElementById(`scopeNote`),j=!0,M=null,N=[],P=[],F=!u,ce=!1,I={feedback_categories:Object.keys(a),correction_forbidden:[]},L=I.feedback_categories[0],R=`sentence`;u&&(S.textContent=`피드백 전송`);function z(){if(typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=new Uint8Array(16);return crypto.getRandomValues(e),`feedback-${Date.now().toString(36)}-${Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}`}function B(){try{return JSON.parse(localStorage.getItem(w)||`[]`)}catch{return[]}}function V(){try{return localStorage.setItem(w,JSON.stringify(P)),!0}catch{return!1}}function H(e){re.textContent=e,clearTimeout(H.timer),H.timer=setTimeout(()=>{re.textContent=`문장을 눌러 피드백을 남기세요.`},2200)}function U(){return I.correction_forbidden.includes(L)}function W(){return M?R===`paragraph`?{start:M.paragraphStart,end:M.paragraphEnd,text:M.paragraphText}:{start:M.start,end:M.end,text:M.text}:null}function G(){for(let e of O.children){let t=e.dataset.mode===R;e.classList.toggle(`active`,t),e.setAttribute(`aria-checked`,String(t))}let e=R===`word`;ae.hidden=!e,k.hidden=!e,J();let t=W();t&&(ne.textContent=`${M.page}${M.slide?` · ${M.slide}`:``}\n${t.text.trim()}`)}function K(){for(let e of D.children)e.classList.toggle(`active`,e.dataset.category===L),e.setAttribute(`aria-checked`,String(e.dataset.category===L));let e=U();A.hidden=e,b.hidden=e,e&&(b.value=``),A.textContent=o.has(L)?`수정문 · 필수`:`수정문 · 선택 사항`,J()}function q(){return n({category:L,onceOnly:C.checked,term:R===`word`?k.value.normalize(`NFC`).trim():void 0,selectedText:W()?.text??``})}function J(){let t=q();oe.hidden=t.blocked===`category`,se.textContent=t.blocked?e(t.blocked):t.scope===`personal`?r:`이번 문서에만 적용됩니다.`}function le(){D.setAttribute(`role`,`radiogroup`),D.setAttribute(`aria-label`,`문제 유형`),D.replaceChildren(),I.feedback_categories.includes(L)||(L=I.feedback_categories[0]);for(let e of I.feedback_categories){let t=document.createElement(`button`);t.type=`button`,t.dataset.category=e,t.setAttribute(`role`,`radio`),t.textContent=a[e]||e,t.addEventListener(`click`,()=>{L=e,K()}),D.append(t)}K()}function Y(e){ce||(ce=!0,be(),H(`원본에서 문장을 연결할 수 없어 검수용 보기로 전환합니다. (${e})`),u&&setTimeout(()=>{location.href=`/#review=${encodeURIComponent(l)}`},700))}async function X(){let e=await i(l);return d=e.job.id,e}async function ue(){if(!u)return!0;let e=await X();if(e.draft_text!==window.__feedbackDraftText)throw Error(`text-mismatch`);return fe(e),!0}async function de(e){let t=ee,n=null;if(!t&&u){let r=e??await X();t=r.surface?.target_url??null,n=r.surface?.selectors??null}if(!t)throw Error(`missing-target`);let r=new URL(t,location.href);if(r.origin!==location.origin)throw Error(`cross-origin-target`);return n&&!c.get(`selectors`)&&(T=n),r.href}function fe(e){Array.isArray(e.support?.feedback_categories)&&e.support.feedback_categories.length>0&&(I={feedback_categories:e.support.feedback_categories,correction_forbidden:e.support.correction_forbidden??[]},le())}async function pe(){let e=null;if(u&&!ee)try{e=await X()}catch(e){Y(e.message||`review-unavailable`);return}if(e?.surface?.snapshot){fe(e),me(e);return}try{p=await de(e)}catch(e){Y(e.message||`surface-unavailable`);return}w=`gyojeong:${l||`local`}:${p}`,P=B(),Q(),f.src=p}function me(e){m=e.surface.snapshot,h=e.draft_text,p=e.surface.target_url,w=`gyojeong:${l||`local`}:snapshot:${p}`,P=B(),Q(),f.hidden=!0,E.hidden=!1,F=!0,ge(),H(`원본을 캡처한 화면에 검수 레이어를 연결했습니다.`)}function he(e,t){return e.content_base64?(t.src=`data:${e.media_type};base64,${e.content_base64}`,Promise.resolve()):Promise.reject(Error(`tile-missing`))}function ge(){E.replaceChildren();let e=E.clientWidth/m.page_width,t=document.createElement(`div`);t.className=`surface-page`,t.style.width=`${m.page_width*e}px`,t.style.height=`${m.page_height*e}px`,E.append(t);for(let n of m.tiles){let r=document.createElement(`img`);r.alt=``,r.className=`surface-tile`,r.style.top=`${n.top*e}px`,r.style.width=`${m.page_width*e}px`,r.style.height=`${n.height*e}px`,t.append(r),he(n,r).catch(()=>Y(`tile-unavailable`))}if(j)for(let[n,r]of m.units.entries()){let i=`unit-${n}`,a=[...h].slice(r.start,r.end).join(``),o=P.some(e=>e.start===r.start&&e.end===r.end);for(let[n,[s,c,l,u]]of r.rects.entries()){let d=document.createElement(`button`);d.type=`button`,d.className=`surface-hit`,d.dataset.group=i,d.dataset.pinned=String(o),n===0?d.setAttribute(`aria-label`,`${a} 피드백 남기기`):(d.tabIndex=-1,d.setAttribute(`aria-hidden`,`true`)),d.style.left=`${s*e}px`,d.style.top=`${c*e}px`,d.style.width=`${l*e}px`,d.style.height=`${u*e}px`,d.addEventListener(`click`,e=>{e.preventDefault(),Se({group:i,text:a,start:r.start,end:r.end,paragraphText:r.block?[...h].slice(r.block[0],r.block[1]).join(``):a,paragraphStart:r.block?r.block[0]:r.start,paragraphEnd:r.block?r.block[1]:r.end,slide:``,page:``})}),t.append(d)}}}function _e(e){let t=[],n=e.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode(e){return!e.data.trim()||e.parentElement.closest(`.notes,[aria-hidden="true"],script,style`)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}});for(;n.nextNode();)t.push(n.currentNode);return t}function ve(e,t){let n=0;for(let r of e){let e=n+r.data.length;if(t<=e)return{node:r,offset:Math.max(0,t-n)};n=e}let r=e.at(-1);return r?{node:r,offset:r.data.length}:null}function ye(e){let t=_e(e);if(!t.length)return[];let n=t.map(e=>e.data).join(``);return[...new Intl.Segmenter(`ko`,{granularity:`sentence`}).segment(n)].flatMap(n=>{let r=n.segment,i=r.search(/\S/);if(i<0)return[];let a=r.length-r.trimEnd().length,o=ve(t,n.index+i),s=ve(t,n.index+r.length-a);if(!o||!s)return[];let c=e.ownerDocument.createRange();try{c.setStart(o.node,o.offset),c.setEnd(s.node,s.offset)}catch{return[]}return[{text:r.trim(),range:c,start:n.index+i,end:n.index+r.length-a}]})}function be(){for(let e of N)e.remove();N=[]}function Z(){be();let e;try{e=f.contentDocument}catch{Y(`cross-origin`);return}if(!e?.body||!j)return;let t=e.createElement(`div`);t.id=`feedback-overlay-root`,Object.assign(t.style,{position:`absolute`,inset:`0`,zIndex:`2147483646`,pointerEvents:`none`}),e.body.append(t),N.push(t);let n=[...e.querySelectorAll(T)].filter(e=>!e.closest(`.notes,.controls,.note-panel,#feedback-overlay-root,[aria-hidden="true"]`)&&e.textContent.trim().length>1),r=n.map(e=>_e(e).map(e=>e.data).join(``));window.__feedbackDraftText=r.join(`
2
-
3
- `);let i=0,a=0;for(let[o,s]of n.entries()){let c=r[o],l=a;for(let n of ye(s)){let r=`sentence-${i++}`,a=[...n.range.getClientRects()].filter(e=>e.width>2&&e.height>2);for(let[i,o]of a.entries()){let a=e.createElement(`button`);a.type=`button`,a.disabled=!F,a.dataset.group=r,i===0?a.setAttribute(`aria-label`,`${n.text} 피드백 남기기`):(a.tabIndex=-1,a.setAttribute(`aria-hidden`,`true`));let u=P.some(e=>e.group===r||e.text===n.text&&e.slide===(s.closest(`.slide`)?.dataset.title||``));a.dataset.pinned=String(u),Object.assign(a.style,{position:`absolute`,left:`${o.left+f.contentWindow.scrollX}px`,top:`${o.top+f.contentWindow.scrollY}px`,width:`${o.width}px`,height:`${o.height}px`,margin:`0`,padding:`0`,border:`1px solid transparent`,borderRadius:`2px`,background:u?`rgba(0,0,0,.18)`:`rgba(0,0,0,.01)`,borderColor:u?`rgba(0,0,0,.78)`:`transparent`,cursor:F?`pointer`:`progress`,pointerEvents:`auto`}),a.addEventListener(`mouseenter`,()=>xe(e,r,!0)),a.addEventListener(`mouseleave`,()=>xe(e,r,!1)),a.addEventListener(`click`,e=>{e.preventDefault(),e.stopPropagation(),Se({group:r,text:n.text,start:l+Array.from(c.slice(0,n.start)).length,end:l+Array.from(c.slice(0,n.end)).length,paragraphText:c,paragraphStart:l,paragraphEnd:l+Array.from(c).length,slide:s.closest(`.slide`)?.dataset.title||``,page:s.closest(`.canvas`)?.dataset.page||``})}),t.append(a)}}a+=Array.from(c).length+(o<n.length-1?2:0)}}function xe(e,t,n){for(let r of e.querySelectorAll(`[data-group="${t}"]`)){let e=r.dataset.pinned===`true`;r.style.borderColor=n||e?`rgba(0,0,0,.78)`:`transparent`,r.style.background=n?`rgba(0,0,0,.10)`:e?`rgba(0,0,0,.18)`:`rgba(0,0,0,.01)`}}function Se(e){M=e,R=`sentence`,b.value=``,k.value=``,C.checked=!1,G(),K(),y.classList.add(`open`),x.hidden=!1}function Q(){let e=P.filter(e=>!e.submitted);if(te.textContent=String(P.length),S.disabled=e.length===0,v.replaceChildren(),!P.length){let e=document.createElement(`p`);e.className=`item`,e.textContent=`아직 담긴 피드백이 없습니다.`,v.append(e);return}P.forEach((e,t)=>{let n=document.createElement(`div`);n.className=`item`;let r=document.createElement(`b`),i=a[e.category]||e.category;r.textContent=`${e.page}${e.slide?` · ${e.slide}`:``} · ${i}${e.term?` · ${e.term}`:``}${e.submitted?` · 전송됨`:``}`;let o=document.createElement(`p`);o.textContent=e.text;let s=document.createElement(`p`);s.textContent=e.correction||`수정 제안 없음`;let c=document.createElement(`button`);c.type=`button`,c.textContent=`제거`,c.addEventListener(`click`,()=>{P.splice(t,1);let e=V();Q(),$(),e||H(`검수함 변경은 현재 탭에서만 유지됩니다.`)}),n.append(r,o,s,c),v.append(n)})}f.addEventListener(`load`,async()=>{try{if(Z(),!f.contentDocument?.body){Y(`iframe-access`);return}await ue(),F=!0,Z()}catch(e){Y(e.message||`validation-failed`);return}H(`원본 iframe에 검수 레이어를 연결했습니다.`)});var $=()=>m?ge():Z(),Ce;addEventListener(`resize`,()=>{clearTimeout(Ce),Ce=setTimeout($,120)}),g.addEventListener(`click`,()=>{j=!j,g.textContent=j?`검수 모드`:`읽기 모드`,g.setAttribute(`aria-pressed`,String(j)),ie.textContent=j?`검수 중`:`읽기 중`,j||(y.classList.remove(`open`),x.hidden=!0),$()}),C.addEventListener(`change`,J),k.addEventListener(`input`,J),O.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-mode]`);!t||!M||(R=t.dataset.mode,G())}),document.getElementById(`showBasket`).addEventListener(`click`,()=>{_.hidden=!_.hidden,Q()}),document.getElementById(`closeBasket`).addEventListener(`click`,()=>{_.hidden=!0}),document.getElementById(`cancel`).addEventListener(`click`,()=>{y.classList.remove(`open`),x.hidden=!0}),document.getElementById(`add`).addEventListener(`click`,()=>{if(!M){H(`선택한 문장을 다시 눌러 주세요.`);return}let e=U()?``:b.value.normalize(`NFC`).trim();if(o.has(L)&&!e){H(`이 분류에는 수정문이 필요합니다.`),b.focus();return}let t=W(),n=R===`word`?k.value.normalize(`NFC`).trim():``;if(R===`word`){if(!n){H(`어떤 표현에 대한 지적인지 적어 주세요.`),k.focus();return}if(!t.text.normalize(`NFC`).includes(n)){H(`대상 표현이 선택한 문장 안에 없습니다.`),k.focus();return}}P.push({...M,id:z(),category:L,mode:R,start:t.start,end:t.end,text:t.text,...n?{term:n}:{},scope:q().scope,correction:e,created_at:new Date().toISOString(),submitted:!1});let r=V();y.classList.remove(`open`),x.hidden=!0,Q(),$(),H(r?`검수함에 추가했습니다.`:`검수함에 추가했습니다. 현재 탭에서만 유지됩니다.`)}),document.getElementById(`copyFeedback`).addEventListener(`click`,async()=>{if(!u){await navigator.clipboard.writeText(JSON.stringify({source:f.src,feedback:P},null,2)),H(`피드백 JSON을 복사했습니다.`);return}if(!d){H(`전송 실패: 작업 정보를 아직 불러오지 못했습니다.`);return}let e=P.filter(e=>!e.submitted);S.disabled=!0,H(`${e.length}개 피드백을 전송하고 있습니다.`);try{let n=await t(l,e.map(e=>({input:{job_id:d,category:e.category,scope:e.scope||`run_only`,selection:{start:e.start,end:e.end,selected_text:e.text},...e.term?{term:e.term}:{},...e.correction?{correction:e.correction}:{}},idempotencyKey:e.id||z()})));e.forEach((e,t)=>{e.submitted=!0,e.feedback_id=n[t].feedback_id});let r=V();Q(),$(),H(r?`피드백을 전송했습니다.`:`피드백을 전송했습니다. 브라우저 검수함 상태는 저장하지 못했습니다.`)}catch(e){Q(),H(`전송 실패: ${s(e,`다시 시도해 주세요.`)}`)}}),le(),Q(),pe();
@@ -1 +0,0 @@
1
- :root{--ink:#000;--paper:#fff;--muted:#6b6b6b;--rule:#d4d4d4;--wash:#f2f2f2;--serif:"Apple SD Gothic Neo", "Noto Serif CJK KR", "Nanum Myeongjo", "AppleMyungjo", serif;--mono:"SF Mono", "Menlo", "D2Coding", "Consolas", monospace;--column:41rem;--bar-height:54px;--safe-bottom:env(safe-area-inset-bottom,0px);--sheet-bottom:calc(var(--bar-height) + var(--safe-bottom));--sheet-max-height:62dvh;color:var(--ink);background:var(--paper);font-family:var(--mono);font-synthesis:none;text-rendering:optimizelegibility}*{box-sizing:border-box}body{background:var(--paper);min-width:320px;min-height:100dvh;margin:0}button,select,textarea,input{font:inherit;color:inherit}button,select,textarea,[tabindex]{outline-offset:2px;outline:2px solid #0000}button:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline-color:var(--ink)}.desk{width:min(var(--column) + 48px, 100vw - 32px);margin:0 auto;padding:0 0 40dvh}.topline{z-index:5;border-bottom:1px solid var(--ink);background:var(--paper);align-items:baseline;gap:14px;padding:18px 0 10px;display:flex;position:sticky;top:0}.route{letter-spacing:.06em;text-transform:lowercase;margin:0;font-size:.7rem}.badge{color:var(--muted);letter-spacing:.14em;text-transform:uppercase;margin-right:auto;font-size:.66rem}.mode-toggle{border:1px solid var(--ink);background:var(--paper);cursor:pointer;letter-spacing:.08em;min-height:30px;padding:0 10px;font-size:.68rem}.mode-toggle[aria-pressed=true]{background:var(--ink);color:var(--paper)}.doc{max-width:var(--column);font-family:var(--serif);word-break:keep-all;overflow-wrap:anywhere;margin:34px auto 0;font-size:1.08rem;line-height:2.05}.doc-block{margin:0 0 1.35em}.doc-block.doc-h1,.doc-block.doc-h2,.doc-block.doc-h3,.doc-block.doc-h4{font-family:var(--mono);letter-spacing:.2em;text-transform:uppercase;margin:2.4em 0 .9em;font-size:.72rem;font-weight:400;line-height:1.5}.doc-block.doc-h1:before,.doc-block.doc-h2:before,.doc-block.doc-h3:before{content:"";border-top:1px solid var(--ink);width:100%;margin-bottom:.9em;display:block}.doc-block.doc-pre{font-family:var(--mono);font-size:.82rem;line-height:1.7}.doc-block.doc-pre pre{border-left:2px solid var(--ink);background:var(--wash);margin:0;padding:14px 16px;overflow-x:auto}.doc code{font-family:var(--mono);font-size:.86em}.doc a{color:inherit;text-underline-offset:3px;text-decoration-thickness:1px}.doc table{border-collapse:collapse;font-size:.9rem;display:block;overflow-x:auto}.doc :is(th,td){border:1px solid var(--rule);padding:6px 10px}.doc blockquote{border-left:2px solid var(--ink);margin:0;padding-left:14px}.doc img{max-width:100%;height:auto}.preview-attachment{max-width:min(100%,760px);margin:18px 0;display:block}.preview-attachment img{border:1px solid var(--rule);object-fit:contain;max-width:100%;max-height:70vh;display:block}.preview-attachment figcaption{color:var(--muted);font-family:var(--mono);margin-top:6px;font-size:.76rem}.preview-image-note{color:var(--muted);font-family:var(--mono);font-size:.76rem;display:inline-block}.unit{cursor:pointer;transition:background .12s linear,color .12s linear}.doc:not(.is-reading) .unit:hover,.doc:not(.is-reading) .unit:focus-visible,.doc:not(.is-reading) .unit.is-hover-group{text-underline-offset:5px;outline:none;text-decoration:underline;text-decoration-thickness:1px}.doc.is-reading .unit{cursor:text}.unit.is-pinned{background:var(--ink);color:var(--paper);padding:0 1px;text-decoration:none}.unit.is-pinned :is(code,a,strong,em){color:inherit}.pin{font-family:var(--mono);vertical-align:super;margin-left:3px;font-size:.62rem;font-weight:400}.pin-scope{gap:8px;margin:0 0 12px;display:flex}.pin-scope .chip:disabled{opacity:.4;cursor:default}.pin-term{box-sizing:border-box;border:1px solid var(--rule);width:100%;min-height:44px;font-family:var(--mono);margin:0 0 12px;padding:10px 12px;font-size:.92rem}.pin-siblings{border-top:1px solid var(--rule);flex-wrap:wrap;gap:8px;margin:12px 0 0;padding-top:12px;display:flex}.pin-sibling{border:1px solid var(--rule);min-height:44px;font-family:var(--mono);color:var(--muted);cursor:pointer;background:0 0;padding:0 12px;font-size:.78rem}.pin-siblings[hidden]{display:none}.source-panel{max-width:var(--column);border:1px solid var(--rule);margin:24px auto 0}.source-panel>summary{box-sizing:border-box;min-height:44px;font-family:var(--mono);color:var(--muted);cursor:pointer;align-items:center;padding:12px 14px;font-size:.78rem;display:flex}.source-text{border-top:1px solid var(--rule);font-family:var(--serif);color:var(--muted);white-space:pre-wrap;word-break:keep-all;overflow-wrap:anywhere;padding:14px;font-size:.98rem;line-height:1.85}.review-bar{z-index:20;padding:12px max(16px, calc((100vw - var(--column)) / 2));padding-bottom:calc(12px + var(--safe-bottom));background:var(--ink);color:var(--paper);letter-spacing:.08em;align-items:center;gap:16px;font-size:.72rem;display:flex;position:fixed;bottom:0;left:0;right:0}.bar-mode{text-transform:uppercase}.bar-count{color:#b3b3b3;margin-right:auto}.bar-add,.bar-send,.bar-finish{cursor:pointer;letter-spacing:.08em;white-space:nowrap;min-height:30px;padding:0 14px;font-size:.72rem}.bar-add,.bar-finish{color:var(--paper);background:0 0;border:1px solid #6b6b6b}.bar-send{border:1px solid var(--paper);background:var(--paper);color:var(--ink)}.bar-add:disabled,.bar-send:disabled,.bar-finish:disabled{color:#6b6b6b;cursor:default;background:0 0;border-color:#4d4d4d}.review-bar .status{color:#b3b3b3;margin:0}.pin-card{left:0;right:0;bottom:var(--sheet-bottom);z-index:19;max-height:var(--sheet-max-height);padding:22px max(16px, calc((100vw - var(--column)) / 2)) 26px;background:var(--ink);color:var(--paper);border-top:1px solid #4d4d4d;transition:bottom .16s ease-out,max-height .16s ease-out;animation:.16s ease-out sheet-up;position:fixed;overflow-y:auto}@keyframes sheet-up{0%{opacity:0;transform:translateY(12px)}}.pin-card-head{letter-spacing:.16em;text-transform:uppercase;justify-content:space-between;align-items:baseline;margin-bottom:12px;font-size:.68rem;display:flex}.pin-card-close{min-width:30px;min-height:30px;color:var(--paper);cursor:pointer;background:0 0;border:0;font-size:1rem}.pin-quote{border-left:2px solid var(--paper);font-family:var(--serif);max-height:7.2em;margin:0 0 20px;padding-left:12px;font-size:1rem;line-height:1.8;overflow-y:auto}.label{color:#b3b3b3;letter-spacing:.18em;text-transform:uppercase;margin:0 0 8px;font-size:.62rem}.chips{flex-wrap:wrap;gap:6px;margin-bottom:20px;display:flex}.chip{min-height:30px;color:var(--paper);cursor:pointer;background:0 0;border:1px solid #6b6b6b;padding:0 11px;font-size:.72rem}.chip.is-on{border-color:var(--paper);background:var(--paper);color:var(--ink)}.pin-correction{width:100%;min-height:76px;color:var(--paper);font-family:var(--serif);resize:vertical;background:0 0;border:1px solid #6b6b6b;border-radius:0;padding:10px 12px;font-size:.98rem}.pin-correction::placeholder{color:#6b6b6b}.pin-remember{align-items:center;gap:9px;min-height:42px;font-size:.72rem;display:flex}.pin-remember[hidden]{display:none}.pin-remember input{width:14px;height:14px;accent-color:var(--paper)}.pin-actions{align-items:center;gap:10px;margin:2px 0 18px;display:flex}.pin-actions[hidden]{display:none}.pin-remove{color:#b3b3b3;cursor:pointer;text-underline-offset:3px;background:0 0;border:0;min-height:34px;font-size:.72rem;text-decoration:underline}.pin-card .status{color:#b3b3b3;margin:10px 0 0;font-size:.72rem}.pin-actions .status{margin:0}.terminal-state{max-width:var(--column);margin:22vh auto 0}.terminal-state h1{letter-spacing:.2em;text-transform:uppercase;margin:0 0 10px;font-size:.78rem;font-weight:400}.terminal-state p,.empty-note,.status{color:var(--muted);margin:0;font-size:.78rem}@media (width<=720px){.review-bar{padding-top:6px;padding-left:12px;padding-right:12px;padding-bottom:calc(6px + var(--safe-bottom));gap:6px}.bar-add,.bar-send,.bar-finish{padding-left:10px;padding-right:10px}.doc{font-size:1.02rem;line-height:1.95}.pin-card{padding-bottom:12px;padding-left:16px;padding-right:16px}.pin-actions{margin-bottom:0}}@media (pointer:coarse){.chip,.pin-card-close,.pin-remove,.bar-add,.bar-send,.bar-finish,.mode-toggle{min-height:44px}.chip{padding:0 14px}.pin-card-close{min-width:44px}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.001ms!important;animation-duration:.001ms!important}}