{"release_id":"01a1998d-9802-0000-b0f7-33a23307f4ed","chunk_id":"01a1998d-994f-7141-ad23-a4403bddf4c3","file":"QDVMtnh8.mjs","mappings":"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","names":["OBJECTS","msg","OBJECT_BY_KEY","o","AVERAGE","BJU_2015","LIMITS","diameterCm","girthCm","FLACCID_GIRTH_RATIO","flaccidGirthCm","AVERAGE_GROWTH","growth","size","ratio","kind","toUnit","cm","unit","fromUnit","v","clamp","lo","hi","formatLength","locale","n","num","raw","limits","readSize","params","suffix","lengthCm","flaccid","readComparison","defaultUnit","object","writeComparison","c","p","put","k"],"sources":["../../../../../../src/lib/tools/size-scale.ts"],"sourcesContent":["import { BJU_2015 } from '$lib/data/size-stats';\nimport { msg } from '$lib/i18n/mark';\n\n/**\n * Drawing sizes to scale (RAT-575): the visualizer, the size chart and the\n * calculator all put a measurement next to the average and next to things\n * everyone has held. Everything here is in centimetres; the pages convert for\n * display only.\n */\n\nexport type Unit = 'cm' | 'in';\n\n/** Everyday objects, upright, at their real size. */\nexport const OBJECTS = [\n\t// ISO/IEC 7810 ID-1, the size of every bank card\n\t{ key: 'card', label: msg('bank card'), heightCm: 8.56, widthCm: 5.4 },\n\t// a 330–355 ml can: 11.5–12.2 cm tall, 6.6 cm across\n\t{ key: 'can', label: msg('soda can'), heightCm: 12.2, widthCm: 6.6 },\n\t// a 6.1-inch phone, the most common size\n\t{ key: 'phone', label: msg('phone'), heightCm: 14.7, widthCm: 7.1 }\n] as const;\nexport type ObjectKey = (typeof OBJECTS)[number]['key'];\nexport const OBJECT_BY_KEY = Object.fromEntries(OBJECTS.map((o) => [o.key, o])) as Record<\n\tObjectKey,\n\t(typeof OBJECTS)[number]\n>;\n\nexport interface Size {\n\t/** erect length */\n\tlengthCm: number;\n\t/** erect circumference */\n\tgirthCm: number;\n\tflaccidCm: number;\n}\n\nexport const AVERAGE: Size = {\n\tlengthCm: BJU_2015.erectLength.meanCm,\n\tgirthCm: BJU_2015.erectCircumference.meanCm,\n\tflaccidCm: BJU_2015.flaccidLength.meanCm\n};\n\n/** Input limits, in cm: past these the measurement is almost certainly wrong. */\nexport const LIMITS = {\n\tlengthCm: [5, 26],\n\tgirthCm: [6, 20],\n\tflaccidCm: [3, 20]\n} as const;\n\nexport const diameterCm = (girthCm: number) => girthCm / Math.PI;\n\n/** Flaccid girth is not asked for: it keeps the average flaccid/erect ratio. */\nconst FLACCID_GIRTH_RATIO =\n\tBJU_2015.flaccidCircumference.meanCm / BJU_2015.erectCircumference.meanCm;\nexport const flaccidGirthCm = (girthCm: number) => girthCm * FLACCID_GIRTH_RATIO;\n\n/** Average growth from flaccid to erect in Veale 2015: 9.16 → 13.12 cm, +43%. */\nexport const AVERAGE_GROWTH = AVERAGE.lengthCm / AVERAGE.flaccidCm - 1;\n\n/** How much a size grows, against the average's 43%. A typical spread is\n *  ±10 points; past it, \"grower\" and \"shower\" are the words people use. */\nexport function growth(size: Size): {\n\tcm: number;\n\tratio: number;\n\tkind: 'grower' | 'shower' | 'typical';\n} {\n\tconst ratio = size.lengthCm / size.flaccidCm - 1;\n\tconst kind =\n\t\tratio > AVERAGE_GROWTH + 0.1 ? 'grower' : ratio < AVERAGE_GROWTH - 0.1 ? 'shower' : 'typical';\n\treturn { cm: size.lengthCm - size.flaccidCm, ratio, kind };\n}\n\nexport const toUnit = (cm: number, unit: Unit) => (unit === 'cm' ? cm : cm / 2.54);\nexport const fromUnit = (v: number, unit: Unit) => (unit === 'cm' ? v : v * 2.54);\n\nexport const clamp = (v: number, [lo, hi]: readonly [number, number]) =>\n\tMath.min(hi, Math.max(lo, v));\n\n/** A measurement for display: one decimal, the reader's decimal separator. */\nexport function formatLength(cm: number, unit: Unit, locale = 'en'): string {\n\tconst v = toUnit(cm, unit);\n\tconst n = v.toLocaleString(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 });\n\treturn unit === 'cm' ? `${n} cm` : `${n} in`;\n}\n\n/** Which object a length is closest to, and by how much it differs. */\nexport function nearestObject(lengthCm: number) {\n\tlet best: (typeof OBJECTS)[number] = OBJECTS[0];\n\tfor (const o of OBJECTS) {\n\t\tif (Math.abs(o.heightCm - lengthCm) < Math.abs(best.heightCm - lengthCm)) best = o;\n\t}\n\treturn { object: best, diffCm: lengthCm - best.heightCm };\n}\n\n// ── share links ──────────────────────────────────────────────────────────────\n// A comparison travels in its address, in cm with one decimal: nothing about\n// it is stored anywhere, and a shared link opens on the same picture.\n\nexport interface Comparison {\n\tyou: Size;\n\tthem: Size | null;\n\tthemName: string;\n\tunit: Unit;\n\tflaccid: boolean;\n\tobject: ObjectKey;\n}\n\nconst num = (raw: string | null, limits: readonly [number, number]) => {\n\tif (raw === null || raw === '') return null;\n\tconst v = Number(raw);\n\treturn Number.isFinite(v) ? clamp(Math.round(v * 10) / 10, limits) : null;\n};\n\nfunction readSize(params: URLSearchParams, suffix: string): Size | null {\n\tconst lengthCm = num(params.get(`l${suffix}`), LIMITS.lengthCm);\n\tif (lengthCm === null) return null;\n\tconst girthCm = num(params.get(`g${suffix}`), LIMITS.girthCm) ?? AVERAGE.girthCm;\n\tconst flaccid = num(params.get(`f${suffix}`), LIMITS.flaccidCm);\n\treturn {\n\t\tlengthCm,\n\t\tgirthCm,\n\t\tflaccidCm: Math.min(flaccid ?? lengthCm / (1 + AVERAGE_GROWTH), lengthCm)\n\t};\n}\n\nexport function readComparison(params: URLSearchParams, defaultUnit: Unit): Comparison {\n\tconst object = params.get('o');\n\treturn {\n\t\tyou: readSize(params, '') ?? { ...AVERAGE },\n\t\tthem: readSize(params, '2'),\n\t\tthemName: (params.get('n2') ?? '').slice(0, 16),\n\t\tunit: params.get('u') === 'in' ? 'in' : params.get('u') === 'cm' ? 'cm' : defaultUnit,\n\t\tflaccid: params.get('s') === 'f',\n\t\tobject: object && object in OBJECT_BY_KEY ? (object as ObjectKey) : 'phone'\n\t};\n}\n\nexport function writeComparison(c: Comparison): URLSearchParams {\n\tconst p = new URLSearchParams();\n\tconst put = (k: string, v: number) => p.set(k, v.toFixed(1));\n\tput('l', c.you.lengthCm);\n\tput('g', c.you.girthCm);\n\tput('f', c.you.flaccidCm);\n\tif (c.them) {\n\t\tput('l2', c.them.lengthCm);\n\t\tput('g2', c.them.girthCm);\n\t\tput('f2', c.them.flaccidCm);\n\t\tif (c.themName.trim()) p.set('n2', c.themName.trim().slice(0, 16));\n\t}\n\tp.set('u', c.unit);\n\tif (c.flaccid) p.set('s', 'f');\n\tif (c.object !== 'phone') p.set('o', c.object);\n\treturn p;\n}\n"],"version":3}