776 lines
117 KiB
HTML
776 lines
117 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<title>Spike 50 Parts Board</title>
|
||
<link rel="manifest" href="manifest.webmanifest" crossorigin="use-credentials">
|
||
<meta name="theme-color" content="#1d5fc4">
|
||
<meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-title" content="Spike 50"><link rel="apple-touch-icon" href="icon-192.png">
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Condensed:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
||
<style>
|
||
/* Layout: workshop parts board — drawing on the left, the numbered parts list beside it, status colour on every marker */
|
||
:root{
|
||
--bg:#eef1f4; --panel:#ffffff; --ink:#18212c; --muted:#5d6977; --line:#d5dbe2; --soft:#e4e9ef;
|
||
--accent:#1d5fc4; --accent-ink:#ffffff;
|
||
--have:#1f8a4c; --miss:#c8332b; --ord:#c27a0e; --skip:#7a8796; --none:#9aa6b3;
|
||
--have-bg:#e3f3e9; --miss-bg:#fbe6e4; --ord-bg:#fbf0dc; --skip-bg:#eceff2;
|
||
--f-display:"IBM Plex Sans Condensed","Arial Narrow",system-ui,sans-serif;
|
||
--f-body:"IBM Plex Sans",system-ui,-apple-system,"Segoe UI",sans-serif;
|
||
--f-mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace;
|
||
}
|
||
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
|
||
--bg:#12171d; --panel:#1a212a; --ink:#e6ebf0; --muted:#98a4b2; --line:#2c3642; --soft:#232c37;
|
||
--accent:#5b97ef; --accent-ink:#0d1520;
|
||
--have:#4cc27c; --miss:#ef6b62; --ord:#e7a93e; --skip:#8d99a7; --none:#6b7785;
|
||
--have-bg:#173226; --miss-bg:#3a1d1c; --ord-bg:#382b13; --skip-bg:#232b34; color-scheme:dark}}
|
||
:root[data-theme="dark"]{
|
||
--bg:#12171d; --panel:#1a212a; --ink:#e6ebf0; --muted:#98a4b2; --line:#2c3642; --soft:#232c37;
|
||
--accent:#5b97ef; --accent-ink:#0d1520;
|
||
--have:#4cc27c; --miss:#ef6b62; --ord:#e7a93e; --skip:#8d99a7; --none:#6b7785;
|
||
--have-bg:#173226; --miss-bg:#3a1d1c; --ord-bg:#382b13; --skip-bg:#232b34; color-scheme:dark}
|
||
*{box-sizing:border-box}
|
||
[hidden]{display:none!important}
|
||
html{color-scheme:light}
|
||
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--f-body);font-size:14px;line-height:1.45;padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));padding-block:16px 40px}
|
||
.wrap{max-width:1400px;margin:0 auto;display:grid;gap:14px}
|
||
h1,h2,h3{font-family:var(--f-display);margin:0;text-wrap:balance}
|
||
.mono{font-family:var(--f-mono);font-variant-numeric:tabular-nums}
|
||
button{font:inherit;color:inherit;cursor:pointer}
|
||
button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
||
a{color:var(--accent)}
|
||
/* header */
|
||
.head{display:flex;flex-wrap:wrap;gap:12px 24px;align-items:end;justify-content:space-between}
|
||
.head h1{font-size:30px;font-weight:700;letter-spacing:-.01em}
|
||
.plate{display:flex;flex-wrap:wrap;gap:6px 14px;color:var(--muted);font-size:13px}
|
||
.plate b{color:var(--ink);font-weight:500}
|
||
.tally{display:flex;gap:6px;flex-wrap:wrap}
|
||
.chip{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:3px 10px;font-size:12.5px;font-weight:500;background:var(--soft);white-space:nowrap}
|
||
.dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--none)}
|
||
.s-have .dot,.dot.s-have{background:var(--have)} .s-miss .dot,.dot.s-miss{background:var(--miss)} .s-ord .dot,.dot.s-ord{background:var(--ord)} .s-skip .dot,.dot.s-skip{background:var(--skip)}
|
||
.bar{height:6px;border-radius:3px;background:var(--soft);overflow:hidden;display:flex}
|
||
.bar i{display:block;height:100%}
|
||
/* tabs */
|
||
.tabs{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px;scrollbar-width:thin}
|
||
.tab{flex:none;border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:8px 12px;display:grid;gap:5px;min-width:150px;text-align:left}
|
||
.tab .t{font-family:var(--f-display);font-weight:600;font-size:14.5px}
|
||
.tab .m{font-size:12px;color:var(--muted);display:flex;justify-content:space-between;gap:8px}
|
||
.tab[aria-selected="true"]{border-color:var(--accent);box-shadow:inset 0 -3px 0 var(--accent)}
|
||
.tab.shop .t{color:var(--miss)}
|
||
/* main */
|
||
.main{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(320px,1fr);gap:14px;align-items:start}
|
||
@media (max-width:900px){.main{grid-template-columns:1fr}}
|
||
.card{background:var(--panel);border:1px solid var(--line);border-radius:10px;min-width:0}
|
||
.dhead{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--line)}
|
||
.dhead h2{font-size:18px}
|
||
.tools{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
||
.btn{border:1px solid var(--line);background:var(--panel);border-radius:7px;padding:5px 10px;font-size:13px}
|
||
.btn.on{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
|
||
.btn.primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent);font-weight:500}
|
||
.viewport{overflow:auto;max-height:72vh;background:#fff;border-radius:0 0 10px 10px;position:relative}
|
||
.stage{position:relative;transform-origin:0 0}
|
||
.stage img{display:block;width:100%;height:auto;max-width:none;user-select:none;-webkit-user-drag:none}
|
||
.stage.editing{cursor:crosshair}
|
||
.pin{position:absolute;transform:translate(-50%,-50%);width:30px;height:24px;border-radius:12px;border:2.5px solid var(--none);background:rgba(255,255,255,.15);padding:0;touch-action:none}
|
||
.pin.s-have{border-color:#1f8a4c;background:rgba(31,138,76,.22)} .pin.s-miss{border-color:#c8332b;background:rgba(200,51,43,.25)}
|
||
.pin.s-ord{border-color:#c27a0e;background:rgba(194,122,14,.25)} .pin.s-skip{border-color:#7a8796;background:rgba(122,135,150,.18)}
|
||
.pin.s-none{border-color:#1d5fc4;border-style:dashed;background:rgba(29,95,196,.06)}
|
||
.pin.sel{box-shadow:0 0 0 3px #1d5fc4,0 0 0 6px rgba(29,95,196,.35);z-index:3}
|
||
.pin.orphan{border-color:#8a5cc8;border-style:dotted}
|
||
.stage.editing .pin{cursor:grab}
|
||
.nodraw{padding:28px 20px;display:grid;gap:12px;justify-items:start;color:var(--muted);max-width:60ch}
|
||
.nodraw h3{color:var(--ink);font-size:17px}
|
||
.note{font-size:12.5px;color:var(--muted);padding:8px 12px;border-top:1px solid var(--line)}
|
||
/* list */
|
||
.lhead{padding:10px 12px;border-bottom:1px solid var(--line);display:grid;gap:8px}
|
||
.filters{display:flex;gap:4px;flex-wrap:wrap}
|
||
.filters .btn{padding:3px 9px;font-size:12.5px}
|
||
input[type=search],textarea{width:100%;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:7px;padding:6px 9px;font:inherit}
|
||
.list{max-height:72vh;overflow:auto}
|
||
.row{display:grid;grid-template-columns:44px minmax(0,1fr) auto;gap:10px;align-items:center;padding:8px 12px;border-bottom:1px solid var(--soft);width:100%;background:none;border-left:0;border-right:0;border-top:0;text-align:left}
|
||
.row:hover{background:var(--soft)}
|
||
.row.sel{background:var(--soft);box-shadow:inset 3px 0 0 var(--accent)}
|
||
.ref{font-family:var(--f-mono);font-weight:500;font-size:13px;border:2px solid var(--none);border-radius:10px;text-align:center;padding:1px 0}
|
||
.row.s-have .ref{border-color:var(--have)} .row.s-miss .ref{border-color:var(--miss)} .row.s-ord .ref{border-color:var(--ord)} .row.s-skip .ref{border-color:var(--skip)}
|
||
.rname{font-weight:500;overflow-wrap:anywhere}
|
||
.rsub{font-size:12px;color:var(--muted)}
|
||
.stat{font-size:12px;color:var(--muted);white-space:nowrap}
|
||
.detail{padding:12px;border-bottom:1px solid var(--line);background:var(--soft);display:grid;gap:10px}
|
||
.sbtns{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
|
||
.sbtns button{border:1.5px solid var(--line);background:var(--panel);border-radius:8px;padding:7px 4px;font-weight:500;font-size:13px}
|
||
.sbtns button[data-s="have"][aria-pressed="true"]{background:var(--have-bg);border-color:var(--have);color:var(--have)}
|
||
.sbtns button[data-s="miss"][aria-pressed="true"]{background:var(--miss-bg);border-color:var(--miss);color:var(--miss)}
|
||
.sbtns button[data-s="ord"][aria-pressed="true"]{background:var(--ord-bg);border-color:var(--ord);color:var(--ord)}
|
||
.sbtns button[data-s="skip"][aria-pressed="true"]{background:var(--skip-bg);border-color:var(--skip);color:var(--skip)}
|
||
.vars{display:grid;gap:4px}
|
||
.var{display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px 10px;background:var(--panel);border:1px solid var(--line);border-radius:7px;padding:6px 8px;font-size:12.5px}
|
||
.var .o{font-family:var(--f-mono)}
|
||
.var .d{overflow-wrap:anywhere}
|
||
.tags{grid-column:1/-1;display:flex;gap:4px;flex-wrap:wrap}
|
||
.tag{font-size:11px;border-radius:4px;padding:1px 6px;background:var(--soft);color:var(--muted)}
|
||
.tag.good{background:var(--have-bg);color:var(--have)} .tag.bad{background:var(--miss-bg);color:var(--miss)} .tag.mine{background:var(--accent);color:var(--accent-ink)}
|
||
.var.no{opacity:.55}
|
||
.lbl{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:600}
|
||
.shop{padding:14px;display:grid;gap:16px}
|
||
.shop table{width:100%;border-collapse:collapse;font-size:13px}
|
||
.shop th,.shop td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--soft);vertical-align:top}
|
||
.shop th{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
||
.tblwrap{overflow-x:auto}
|
||
.empty{padding:20px 12px;color:var(--muted)}
|
||
.toast{position:fixed;left:50%;bottom:calc(16px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);background:var(--ink);color:var(--bg);padding:7px 14px;border-radius:8px;font-size:13px;z-index:20}
|
||
.savebadge{font-size:12px;color:var(--muted)}
|
||
.confirm{display:flex;gap:6px;align-items:center;flex-wrap:wrap;font-size:12.5px}
|
||
@media (prefers-reduced-motion:no-preference){.pin{transition:box-shadow .15s}}
|
||
.sub{display:grid;gap:6px}
|
||
.item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:start;background:var(--panel);border:1px solid var(--line);border-radius:7px;padding:6px 8px;font-size:13px}
|
||
.item .when{font-size:11.5px;color:var(--muted)}
|
||
.item p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}
|
||
.item a{overflow-wrap:anywhere}
|
||
.x{border:0;background:none;color:var(--muted);font-size:16px;line-height:1;padding:2px 4px;border-radius:4px}
|
||
.x:hover{color:var(--miss);background:var(--miss-bg)}
|
||
.addrow{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto;gap:6px}
|
||
@media (max-width:480px){.addrow{grid-template-columns:1fr}}
|
||
input[type=url],input[type=text]{width:100%;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:7px;padding:6px 9px;font:inherit}
|
||
|
||
/* photos, viewer, mobile */
|
||
.pin::after{content:"";position:absolute;inset:-8px}
|
||
.who{display:flex;gap:6px;flex-wrap:wrap;align-items:center;font-size:12.5px;color:var(--muted)}
|
||
.who a{color:var(--muted)}
|
||
.who .chip{padding:2px 9px}
|
||
.sumgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px}
|
||
.sum{background:var(--soft);border-radius:8px;padding:8px 10px}
|
||
.sum b{display:block;font-size:18px;font-family:var(--f-mono)}
|
||
.pos{color:var(--have)}.neg{color:var(--miss)}
|
||
.item .amt{font-family:var(--f-mono);font-weight:600;white-space:nowrap}
|
||
.pgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:6px}
|
||
.pgrid.big{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px}
|
||
.ph{position:relative;border:1px solid var(--line);border-radius:8px;overflow:hidden;padding:0;background:var(--soft);aspect-ratio:1}
|
||
.ph img{width:100%;height:100%;object-fit:cover;display:block}
|
||
.ph span{position:absolute;left:0;right:0;bottom:0;padding:14px 6px 4px;font-size:11.5px;color:#fff;background:linear-gradient(transparent,rgba(0,0,0,.7));text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.ph .pend{position:absolute;top:4px;right:4px;background:var(--ord);color:#fff;border-radius:6px;padding:0 5px;font-size:10.5px}
|
||
.gup{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center}
|
||
.tab.photos .t{color:var(--accent)}
|
||
#viewer{position:fixed;inset:0;z-index:50;background:#0b0e12;color:#fff;display:grid;grid-template-rows:auto minmax(0,1fr) auto}
|
||
#viewer[hidden]{display:none}
|
||
.vbar{display:flex;gap:6px;align-items:center;padding:calc(8px + env(safe-area-inset-top,0px)) 10px 8px;background:#111820}
|
||
.vbar button,.vnav{border:1px solid #33404f;background:#1b2530;color:#fff;border-radius:8px;min-width:42px;height:40px;padding:0 12px;font-size:16px}
|
||
.vbar .vt{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;padding:0 6px}
|
||
.vcanvas{position:relative;overflow:hidden;touch-action:none;user-select:none;-webkit-user-select:none}
|
||
.vstage{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform}
|
||
.vstage.white{background:#fff}
|
||
.vstage img{display:block;width:100%;height:100%;pointer-events:none;-webkit-user-drag:none}
|
||
.vstage .pin{transform:translate(-50%,-50%) scale(var(--k,1));width:34px;height:26px;border-width:3px}
|
||
.vstage .pin .lab{position:absolute;left:50%;bottom:calc(100% + 4px);transform:translateX(-50%);background:#18212c;color:#fff;font:600 13px var(--f-mono);padding:1px 7px;border-radius:6px;white-space:nowrap}
|
||
.vnav{position:absolute;top:50%;transform:translateY(-50%);width:44px;height:64px;padding:0;background:rgba(17,24,32,.7);z-index:2}
|
||
.vnav.prev{left:6px}.vnav.next{right:6px}
|
||
.vbottom{background:var(--panel);color:var(--ink);padding:8px 10px calc(10px + env(safe-area-inset-bottom,0px));max-height:42vh;overflow:auto;display:grid;gap:8px}
|
||
.vbottom .cap{font-size:14px;overflow-wrap:anywhere}
|
||
.vchips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;scrollbar-width:thin}
|
||
.vchip{flex:none;min-width:40px;height:36px;border-radius:18px;border:2.5px solid var(--none);background:var(--soft);font:500 13px var(--f-mono);padding:0 8px}
|
||
.vchip.s-have{border-color:var(--have)}.vchip.s-miss{border-color:var(--miss)}.vchip.s-ord{border-color:var(--ord)}.vchip.s-skip{border-color:var(--skip)}
|
||
.vchip.sel{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
|
||
.vsel{display:grid;gap:6px}
|
||
.vsel .nm{font-weight:600;overflow-wrap:anywhere}
|
||
.vsel .nm .mono{color:var(--accent)}
|
||
.vbottom .sbtns button{padding:11px 2px}
|
||
@media (max-width:700px){
|
||
body{padding-inline:max(10px,env(safe-area-inset-left,0px));font-size:15px}
|
||
.head h1{font-size:22px}
|
||
.plate{font-size:12px;gap:2px 10px}
|
||
.tabs{position:sticky;top:0;z-index:5;background:var(--bg);padding:6px 0;margin-top:-6px}
|
||
.tab{min-width:112px;padding:7px 10px}
|
||
.row{padding:12px;grid-template-columns:40px minmax(0,1fr) auto}
|
||
.sbtns button{padding:12px 2px;font-size:14px}
|
||
.btn{padding:9px 12px;font-size:14px}
|
||
.filters .btn{padding:7px 11px;font-size:13px}
|
||
input[type=search],input[type=url],input[type=text],textarea{font-size:16px;padding:9px 10px}
|
||
.viewport{max-height:56vh}
|
||
.list{max-height:none}
|
||
.x{font-size:22px;padding:6px 10px}
|
||
.dhead h2{font-size:17px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="wrap">
|
||
<header class="head">
|
||
<div style="display:grid;gap:6px">
|
||
<h1>Rieju Spike 50 · Parts Board</h1>
|
||
<div class="plate">
|
||
<span>Model <b>Spike 50 (RR/E2/4)</b></span><span>First reg. <b>2005</b></span>
|
||
<span>Engine <b>Minarelli AM6</b></span><span>Frame no. <b class="mono">…550038</b></span><span>Colour <b>Blue</b></span>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;gap:6px;min-width:min(100%,320px)">
|
||
<div class="tally" id="tally"></div>
|
||
<div class="bar" id="bar"></div>
|
||
<div style="display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center"><span class="savebadge" id="savebadge">Loading saved progress…</span><a class="savebadge" href="api/export">Download backup</a></div>
|
||
<div class="who" id="who"></div>
|
||
</div>
|
||
</header>
|
||
|
||
<nav class="tabs" id="tabs" role="tablist" aria-label="Drawing groups"></nav>
|
||
<main id="main"></main>
|
||
</div>
|
||
<div class="toast" id="toast" hidden></div>
|
||
<div id="viewer" hidden role="dialog" aria-modal="true" aria-label="Image viewer">
|
||
<div class="vbar"><button data-v="close" aria-label="Close">✕</button><span class="vt" id="vtitle"></span>
|
||
<button data-v="zout" aria-label="Zoom out">−</button><button data-v="zin" aria-label="Zoom in">+</button><button data-v="fit">Fit</button></div>
|
||
<div class="vcanvas" id="vcanvas"><div class="vstage" id="vstage" hidden></div>
|
||
<button class="vnav prev" data-v="prev" aria-label="Previous" hidden>‹</button><button class="vnav next" data-v="next" aria-label="Next" hidden>›</button></div>
|
||
<div class="vbottom" id="vbottom"></div>
|
||
</div>
|
||
|
||
<script>
|
||
const GROUPS = [{"key":"chassis","title":"Frame & swingarm","refs":[{"r":"1","v":[{"o":"0/000.010.1001","d":"LIMITED EDITION RR CHASSIS (BAST.>=74239)","a":true,"c":""},{"o":"0/000.010.0550","d":"COMPLETE PAINTED CHASSIS","a":false,"c":""},{"o":"0/000.010.1010","d":"LIMITED EDITION RR CHASSIS (BAST.<=74238)","a":false,"c":""},{"o":"0/000.010.1002","d":"LIMITED EDITION RR CHASSIS 2005","a":true,"c":""}]},{"r":"2","v":[{"o":"0/000.020.0510","d":"SWINGING ARM GREY WITHOUT TIE ROD","a":true,"c":""},{"o":"0/000.020.5100","d":"LIMITED EDITION RR BASCULE 2005","a":true,"c":""},{"o":"0/000.020.0550","d":"COMPLETE PAINTED BASCULE","a":true,"c":""}]},{"r":"3","v":[{"o":"0/000.440.5000B","d":"PLASTIC WASHER M6 6.2x14x1.2","a":true,"c":""},{"o":"0/000.980.0581","d":"DISCONTINUED","a":false,"c":""}]},{"r":"4","v":[{"o":"0/000.980.0580","d":"DISCONTINUED","a":false,"c":""}]},{"r":"5","v":[{"o":"0/000.730.0700","d":"BRAKE LEVER","a":false,"c":""}]},{"r":"6","v":[{"o":"0/000.210.0510","d":"COMPLETE KICKSTAND","a":false,"c":""},{"o":"0/000.210.0540","d":"COMPLETE KICKSTAND","a":false,"c":""}]},{"r":"7","v":[{"o":"0/000.980.0541","d":"ENGINE BRACKET 2005","a":true,"c":""},{"o":"0/000.980.0540","d":"ENGINE BRACKET","a":true,"c":""}]},{"r":"8","v":[{"o":"0/000.340.5101B","d":"QUICK NUT M6 (bag of 5)","a":true,"c":""},{"o":"0/000.380.0710","d":"CHAIN TURNBUCKLE ONLY","a":true,"c":""}]},{"r":"9","v":[{"o":"0/000.740.0040","d":"REAR FOOT SUPPORTS","a":false,"c":""},{"o":"0/000.740.0210","d":"REAR FOOT SUPPORTS","a":true,"c":""}]},{"r":"10","v":[{"o":"0/000.380.5000","d":"CHAIN ADJUSTER R/H","a":false,"c":""},{"o":"0/000.380.0690","d":"COMPLETE CHAIN TURNBUCKLE","a":false,"c":""}]},{"r":"11","v":[{"o":"0/000.340.0170B","d":"NUT M6 SELF-LOCKING (bag of 5)","a":true,"c":""},{"o":"0/000.460.0670B","d":"SCREW M8x25 (bag of 5)","a":true,"c":""}]},{"r":"12","v":[{"o":"0/000.440.0160B","d":"WASHER 8.5x17x1.25 (bag of 5)","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.460.0600B","d":"SCREW M6x16","a":true,"c":""},{"o":"0/000.460.0622","d":"SCREW M6x35","a":true,"c":""}]},{"r":"14","v":[{"o":"0/000.340.0180B","d":"NUT M8 SELF-LOCKING (bag of 5)","a":true,"c":""},{"o":"0/000.380.5001","d":"CHAIN ADJUSTER L/H","a":false,"c":""}]},{"r":"15","v":[{"o":"0/000.440.0210","d":"SPRING WASHER","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.740.0061","d":"RIGHT FOOTREST","a":false,"c":""}]},{"r":"17","v":[{"o":"0/000.740.0071","d":"LEFT FOOTREST","a":false,"c":""}]},{"r":"18","v":[{"o":"0/000.360.0720","d":"FRONT RUBBER TANK MOUNT","a":true,"c":""}]},{"r":"19","v":[{"o":"0/000.360.0090","d":"RUBBER TANK MOUNT","a":false,"c":""}]},{"r":"20","v":[{"o":"0/000.680.0060","d":"CLAMP","a":true,"c":""},{"o":"0/000.680.0070","d":"CABLE TIE","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.360.0700","d":"RUBBER TANK HALF-SHAFT","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.360.0790","d":"RUBBER CHAIN SHOE","a":false,"c":""},{"o":"0/000.360.5001","d":"CHAIN SLIDER","a":true,"c":""}]},{"r":"23","v":[{"o":"0/000.710.0300","d":"CHAIN GUIDE","a":false,"c":""},{"o":"0/000.710.0310","d":"COMPLETE CHAIN GUIDE","a":false,"c":""}]},{"r":"24","v":[{"o":"0/000.360.0370","d":"SWING ARM BUSH","a":true,"c":""}]},{"r":"25","v":[{"o":"0/000.190.0460","d":"SWINGARM AXLE","a":false,"c":""}]},{"r":"26","v":[{"o":"0/000.980.0741","d":"REAR BRAKE CALIPER BRACKET","a":false,"c":""},{"o":"0/000.980.0742","d":"REAR BRAKE CALIPER BRACKET J.JUAN","a":true,"c":""},{"o":"0/000.980.0740","d":"REAR BRAKE CALIPER BRACKET","a":false,"c":""}]},{"r":"27","v":[{"o":"0/000.470.0201","d":"LEFT FOOTREST SPRING (FRAME>701877)","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.360.0792","d":"CHAIN ROLLER","a":true,"c":""}]},{"r":"29","v":[{"o":"0/000.470.0010","d":"KICKSTAND RETURN SPRING","a":false,"c":""}]},{"r":"30","v":[{"o":"0/000.470.0760","d":"REAR BRAKE LEVER SPRING","a":true,"c":""}]},{"r":"31","v":[{"o":"0/000.340.0180B","d":"NUT M8 SELF-LOCKING (bag of 5)","a":true,"c":""}]},{"r":"32","v":[{"o":"0/000.480.0731","d":"KICKSTAND SPACER","a":true,"c":""}]},{"r":"33","v":[{"o":"0/000.480.0210","d":"RIDER FOOTREST SPACER","a":true,"c":""}]},{"r":"34","v":[{"o":"0/000.460.0471","d":"SCREW M8x45","a":false,"c":""}]},{"r":"35","v":[{"o":"0/000.460.0490","d":"SCREW M8x55 DIN 933","a":true,"c":""}]},{"r":"36","v":[{"o":"0/000.480.0645","d":"CHAIN ROLLER INNER BUSHING","a":true,"c":""}]},{"r":"37","v":[{"o":"0/000.380.0700","d":"WHEEL STOP TURNBUCKLE","a":true,"c":""}]},{"r":"38","v":[{"o":"0/000.440.0130B","d":"WASHER 6.5x17x1.5 (bag of 5)","a":true,"c":""}]},{"r":"39","v":[{"o":"0/000.340.0186","d":"REAR AXLE NUT","a":true,"c":""}]},{"r":"40","v":[{"o":"0/000.360.0700","d":"RUBBER TANK HALF-SHAFT","a":true,"c":""}]},{"r":"41","v":[{"o":"0/000.470.0200","d":"RIGHT FOOTREST SPRING (FRAME>701877)","a":true,"c":""}]},{"r":"42","v":[{"o":"0/000.390.1695","d":"DECAL SET BLUE 2005","a":true,"c":"C"},{"o":"0/000.390.1693","d":"DECAL SET RED 2005","a":true,"c":"A"},{"o":"0/000.390.1694","d":"DECAL SET BLACK 2005","a":true,"c":"B"},{"o":"0/000.390.1696","d":"DECAL SET YELLOW 2005","a":true,"c":"D"},{"o":"0/000.390.1632","d":"BLUE SPIKE 2002 DECALS SET","a":true,"c":"K"}]},{"r":"43","v":[{"o":"0/000.680.0165","d":"CLAMP 40-60","a":true,"c":""}]},{"r":"44","v":[{"o":"0/000.980.0569","d":"REAR OIL TANK BRACKET","a":false,"c":""}]},{"r":"46","v":[{"o":"0/000.460.0660B","d":"SCREW M8x20 (bag of 5)","a":true,"c":""}]},{"r":"47","v":[{"o":"0/000.460.0720","d":"SCREW 5.5x22","a":true,"c":""}]},{"r":"48","v":[{"o":"0/000.680.0300","d":"RUBBER","a":true,"c":""}]},{"r":"51","v":[{"o":"0/000.460.0745B","d":"SCREW 5.5x19 (bag of 5)","a":true,"c":""}]},{"r":"53","v":[{"o":"0/000.460.1192","d":"CHAIN COVER SCREW","a":false,"c":""}]},{"r":"80","v":[{"o":"0/000.480.0750","d":"BRAKE LEVER SPACER","a":false,"c":""}]},{"r":"100","v":[{"o":"0/000.460.0310","d":"SCREW M6x22","a":true,"c":""}]},{"r":"101","v":[{"o":"0/000.340.0170","d":"NUT M6","a":true,"c":""}]}],"pins":{"42":[[80.56,16.53]],"18":[[19.2,27.59]],"20":[[23.26,27.59],[31.3,33.53]],"21":[[21.47,29.45]],"100":[[49.02,39.81]],"35":[[4.84,45.63]],"19":[[27.63,47.26]],"30":[[11.55,49.36]],"16":[[23.34,48.54]],"41":[[25.45,51.57]],"31":[[31.54,50.99],[14.05,64.26],[39.19,71.36]],"12":[[5.07,54.25],[32.16,53.78],[38.41,55.88],[11.09,61.93],[33.96,71.59],[36.38,73.69]],"11":[[40.98,56.69]],"26":[[67.76,63.1]],"33":[[34.35,64.73]],"34":[[42.78,67.52]],"25":[[53.32,67.52]],"15":[[93.6,69.73]],"29":[[22.09,73.34]],"14":[[95.39,77.88]],"37":[[91.57,78.46]],"38":[[85.09,81.02]],"24":[[62.45,87.54]],"39":[[65.65,90.1]],"23":[[81.89,90.1]],"22":[[68.85,92.78]],"1":[[7.81,33.99]],"2":[[59.72,69.62]],"3":[[46.53,44.35]],"4":[[35.36,21.19]],"5":[[7.49,62.51]],"6":[[29.51,82.89]],"7":[[53.4,85.1]],"8":[[88.99,70.43]],"9":[[26.93,22.7]],"10":[[80.48,63.8]],"13":[[86.57,84.63]],"17":[[39.03,68.1]],"27":[[36.69,65.89]],"28":[[39.73,61.47]],"32":[[23.58,68.1]],"36":[[41.3,62.98]],"40":[[25.68,30.5]],"46":[[26.78,74.39]],"101":[[52.07,38.18]]}},{"key":"controls-steer","title":"Handlebar & controls","refs":[{"r":"1","v":[{"o":"0/000.530.5010","d":"HANDLEBAR GRIPS","a":true,"c":""},{"o":"0/000.530.0520","d":"RUBBER GRIPS SET 22 & 24","a":false,"c":""}]},{"r":"2","v":[{"o":"0/000.530.5000","d":"TWIST GRIP ASSY","a":true,"c":""},{"o":"0/000.530.0510","d":"COMPLETE THROTTLE GRIP 2000","a":false,"c":""}]},{"r":"3","v":[{"o":"0/000.550.0544","d":"THROTTLE CABLE EURO 2","a":true,"c":""},{"o":"0/000.550.0542","d":"THROTTLE CABLE EURO 2 (BAST.<73599)","a":false,"c":""},{"o":"0/000.550.0545","d":"THROTTLE CABLE FOR PRICOL OIL PUMP","a":true,"c":""},{"o":"0/000.550.0543","d":"THROTTLE CABLE 2003","a":true,"c":""},{"o":"0/000.550.0540","d":"THROTTLE CABLE","a":false,"c":""}]},{"r":"4","v":[{"o":"0/000.640.5700","d":"CABLE ADJUSTER","a":true,"c":""},{"o":"0/000.640.0700","d":"TURNBUCKLE & CABLES SET","a":true,"c":""}]},{"r":"5","v":[{"o":"0/000.990.5001","d":"R/H MIRROR","a":true,"c":""}]},{"r":"6","v":[{"o":"0/000.990.5000","d":"L/H MIRROR","a":false,"c":""},{"o":"0/000.990.0070","d":"REAR-VIEW MIRROR","a":true,"c":""}]},{"r":"7","v":[{"o":"0/000.830.0500","d":"COMPLETE CLUTCH LEVER ASSY","a":true,"c":""},{"o":"0/000.830.5001","d":"FULL CLUTCH LEVER","a":true,"c":""}]},{"r":"8","v":[{"o":"0/000.680.0070","d":"CABLE TIE","a":true,"c":""}]},{"r":"9","v":[{"o":"0/000.720.5000","d":"HANDLEBAR LEFT SWITCH","a":true,"c":""},{"o":"0/000.830.0310","d":"CLUTCH LEVER","a":true,"c":""},{"o":"0/000.720.0160","d":"SWITCH COMPLETE","a":false,"c":""}]},{"r":"10","v":[{"o":"0/000.550.0550","d":"CLUTCH CABLE","a":false,"c":""}]},{"r":"11","v":[{"o":"0/000.170.5000","d":"RR PAINTED HANDLEBAR","a":true,"c":""},{"o":"0/000.170.0550","d":"HANDLEBARS","a":false,"c":""},{"o":"0/000.170.5100","d":"HANDLEBAR","a":true,"c":""}]},{"r":"12","v":[{"o":"0/000.720.5001","d":"LIGHT SWITCH","a":true,"c":""},{"o":"0/000.410.0570","d":"HANDLEBAR END CAPS","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.720.0070","d":"FRONT BRAKE STOP SWITCH","a":true,"c":""},{"o":"0/000.640.7208","d":"FRONT BRAKE STOP SWITCH J.JUAN","a":false,"c":""}]},{"r":"14","v":[{"o":"0/000.640.0450","d":"FRONT BRAKE KIT SET","a":false,"c":""},{"o":"0/000.640.0455","d":"MASTER CYLINDER REPAIR KIT","a":false,"c":""}]},{"r":"15","v":[{"o":"0/000.820.0313","d":"MASTER CYLINDER ASSY (FRAME>552371)","a":false,"c":""},{"o":"0/000.820.0300","d":"COMPLETE FRONT BRAKE LEVER ASSY","a":false,"c":""},{"o":"0/000.820.0312","d":"COMPLETE FRONT BRAKE MASTER CYLINDER","a":false,"c":""}]},{"r":"16","v":[{"o":"0/000.830.1001","d":"FRONT BRAKE LEVER","a":false,"c":""},{"o":"0/000.830.0164","d":"FRONT BRAKE HANDLE ONLY","a":true,"c":""},{"o":"0/000.830.1003","d":"FRONT BRAKE LEVER (FRAME>552371)","a":true,"c":""}]},{"r":"17","v":[{"o":"0/000.830.1002","d":"CLUTCH LEVER ONLY","a":true,"c":""},{"o":"0/000.830.0510","d":"CLUTCH HANDLE ONLY","a":false,"c":""}]},{"r":"18","v":[{"o":"0/000.830.5110","d":"CHOKE LEVER","a":true,"c":""},{"o":"0/000.290.0570","d":"FUEL CAP VENT HOSE","a":false,"c":""}]},{"r":"19","v":[{"o":"0/000.290.0247","d":"FRONT BRAKE HOSE","a":true,"c":""},{"o":"0/000.290.0255","d":"FRONT BRAKE HOSE (FRAME>552371)","a":true,"c":""},{"o":"0/000.290.0580","d":"FRONT BRAKE TUBE ONLY","a":true,"c":""}]},{"r":"20","v":[{"o":"0/000.460.1300B","d":"BRAKE HOSE BANJO BOLT (bag of 5)","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.440.1040B","d":"BRAKE BANJO WASHER (bag of 5)","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.550.0548","d":"CHOKE CABLE","a":false,"c":""}]},{"r":"23","v":[{"o":"0/000.720.0170","d":"SWITCH COMPLETE","a":false,"c":""}]},{"r":"24","v":[{"o":"0/000.550.0549","d":"CHOKE CABLE 2003","a":false,"c":""}]},{"r":"25","v":[{"o":"0/000.830.0520","d":"CHOKE HANDLE ONLY","a":false,"c":""}]},{"r":"26","v":[{"o":"0/000.350.5001","d":"THROTTLE HOUSING CAP","a":true,"c":""}]},{"r":"27","v":[{"o":"0/000.720.5000","d":"HANDLEBAR LEFT SWITCH","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.530.5000","d":"TWIST GRIP ASSY","a":true,"c":""}]},{"r":"29","v":[{"o":"0/000.530.5010","d":"HANDLEBAR GRIPS","a":true,"c":""}]},{"r":"30","v":[{"o":"0/000.990.5001","d":"R/H MIRROR","a":true,"c":""}]},{"r":"31","v":[{"o":"0/000.640.5700","d":"CABLE ADJUSTER","a":true,"c":""}]},{"r":"32","v":[{"o":"0/000.550.5003","d":"CLUTCH CABLE","a":false,"c":""}]},{"r":"33","v":[{"o":"0/000.830.5110","d":"CHOKE LEVER","a":true,"c":""}]},{"r":"34","v":[{"o":"0/000.830.0310","d":"CLUTCH LEVER","a":true,"c":""}]},{"r":"35","v":[{"o":"0/000.830.5000","d":"COMPLETE CLUTCH LEVER ASSY 2003","a":true,"c":""}]}]},{"key":"electrics","title":"Electrics & lights","refs":[{"r":"1","v":[{"o":"0/001.000.0082","d":"COMPLETE 2003 DASHBOARD 6-WAY","a":false,"c":""},{"o":"0/001.000.2071","d":"INSTRUMENT CONSOLE ASSY","a":false,"c":""},{"o":"0/001.000.0072","d":"COMPLETE 2003 DASHBOARD","a":false,"c":""}]},{"r":"2","v":[{"o":"0/000.360.0050","d":"RUBBER MOUNT","a":true,"c":""},{"o":"0/000.460.1170B","d":"PLASTIC SCREW M5x16","a":false,"c":""}]},{"r":"3","v":[{"o":"0/000.980.7130","d":"DASH BRACKET FOR KEY IGNITION","a":true,"c":""}]},{"r":"4","v":[{"o":"0/001.060.0041","d":"IGNITION KEY AND STEERING LOCK","a":false,"c":""}]},{"r":"5","v":[{"o":"0/000.720.0070","d":"FRONT BRAKE STOP SWITCH","a":true,"c":""}]},{"r":"6","v":[{"o":"0/000.720.5000","d":"HANDLEBAR LEFT SWITCH","a":true,"c":""}]},{"r":"7","v":[{"o":"0/000.680.0060","d":"CLAMP","a":true,"c":""}]},{"r":"8","v":[{"o":"0/000.160.0551","d":"MAIN WIRING HARNESS","a":true,"c":""},{"o":"0/000.160.0550","d":"MAIN WIRING HARNESS 2003","a":false,"c":""},{"o":"0/000.160.0540","d":"MAIN WIRING HARNESS (Chassis <=45977)","a":true,"c":""}]},{"r":"9","v":[{"o":"0/001.200.5031","d":"INDICATOR RELAY","a":true,"c":""},{"o":"0/001.200.0030","d":"INDICATOR RELAY (Chassis <=45977)","a":false,"c":""}]},{"r":"10","v":[{"o":"0/000.160.0526","d":"TAILLIGHT WIRING","a":true,"c":""}]},{"r":"11","v":[{"o":"0/000.980.2000","d":"TAILLIGHT BRACKET EU","a":true,"c":""},{"o":"0/000.980.0520","d":"TAILLIGHT BRACKET","a":true,"c":""}]},{"r":"12","v":[{"o":"0/000.850.0200","d":"BULB","a":true,"c":""},{"o":"0/000.850.0220","d":"REAR LIGHT BULB 12V","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.150.0510","d":"COMPLETE TAILLIGHT","a":true,"c":""},{"o":"0/000.630.0501","d":"TAILLIGHT LENS EU","a":false,"c":""}]},{"r":"14","v":[{"o":"0/000.600.0250","d":"COMPLETE HEADLIGHT UNIT","a":false,"c":""},{"o":"0/000.470.0700","d":"HEADLIGHT AIM SPRING","a":true,"c":""}]},{"r":"15","v":[{"o":"0/000.600.0210","d":"COMPLETE HEADLIGHT UNIT","a":true,"c":""},{"o":"0/000.460.0100B","d":"HEADLIGHT MOUNT SCREW (bag of 5)","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.850.0055","d":"HEADLIGHT BULB","a":true,"c":""}]},{"r":"17","v":[{"o":"0/000.620.0500","d":"BULB HOLDER","a":false,"c":""},{"o":"0/000.620.0790","d":"HEADLIGHT SOCKET","a":false,"c":""}]},{"r":"18","v":[{"o":"0/000.360.0760","d":"RUBBER CAP","a":true,"c":""}]},{"r":"19","v":[{"o":"0/005.070.4710","d":"VOLTAGE REGULATOR DUCATI","a":false,"c":""}]},{"r":"20","v":[{"o":"0/000.460.0622","d":"SCREW M6x35","a":true,"c":""},{"o":"0/000.460.0620B","d":"SCREW M6x25 (bag of 5)","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.460.1151","d":"TEMPERATURE SENSOR","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.420.5000","d":"HORN (AC)","a":false,"c":""}]},{"r":"23","v":[{"o":"0/000.460.3010","d":"KICKSTAND SWITCH","a":true,"c":""}]},{"r":"24","v":[{"o":"0/000.720.0520","d":"IGNITION COIL 12V","a":true,"c":""}]},{"r":"25","v":[{"o":"0/005.070.2005","d":"SPARK PLUG CAP FACOMSA","a":false,"c":""}]},{"r":"26","v":[{"o":"0/005.110.2022","d":"SPARK PLUG CAP","a":true,"c":""}]},{"r":"27","v":[{"o":"0/005.822.2419","d":"STATOR (listed as lens screw)","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.460.0220","d":"HEADLIGHT AIM SCREW","a":false,"c":""}]},{"r":"29","v":[{"o":"0/000.460.0744","d":"TAILLIGHT SCREW 4.2x50","a":true,"c":""},{"o":"0/000.440.0229","d":"WASHER","a":false,"c":""}]},{"r":"30","v":[{"o":"0/001.060.0070","d":"IGNITION KEY 6-WAY 2003","a":false,"c":""},{"o":"0/000.460.0340","d":"SCREW M8x20","a":true,"c":""},{"o":"0/000.460.3002","d":"TAMPER-PROOF SCREW","a":false,"c":""}]},{"r":"31","v":[{"o":"0/000.460.0744","d":"TAILLIGHT SCREW 4.2x50","a":true,"c":""}]},{"r":"32","v":[{"o":"0/000.160.0527","d":"IGNITION LOOM 6-WAY 2003","a":true,"c":""},{"o":"0/000.160.0522","d":"IGNITION LOOM 4-WAY","a":true,"c":""},{"o":"0/000.460.0651","d":"SCREW M6x45","a":true,"c":""}]},{"r":"33","v":[{"o":"0/000.460.0610B","d":"SCREW M6x20 (bag of 5)","a":true,"c":""}]},{"r":"34","v":[{"o":"0/000.440.0130B","d":"WASHER 6.5x17x1.5 (bag of 5)","a":true,"c":""}]},{"r":"35","v":[{"o":"0/000.340.0170B","d":"NUT M6 (bag of 5)","a":true,"c":""},{"o":"0/000.770.0700","d":"HEADLIGHT STOP","a":true,"c":""}]},{"r":"36","v":[{"o":"0/000.620.1510","d":"HEADLIGHT SOCKET BRACKET","a":true,"c":""},{"o":"0/001.000.0111","d":"SPEEDOMETER ONLY","a":false,"c":""}]},{"r":"37","v":[{"o":"0/000.980.0575","d":"PLASTIC DASH BRACKET (KEY WITH LOCK)","a":true,"c":""},{"o":"0/000.980.0571","d":"PLASTIC DASH BRACKET (KEY WITH LOCK)","a":false,"c":""}]},{"r":"38","v":[{"o":"0/000.150.0510","d":"COMPLETE TAILLIGHT","a":true,"c":""}]},{"r":"39","v":[{"o":"0/000.850.0210","d":"TAILLIGHT BULB","a":false,"c":""}]},{"r":"44","v":[{"o":"0/000.460.1165B","d":"SELF-TAPPING SCREW 3.5x12 (bag of 5)","a":true,"c":""}]},{"r":"45","v":[{"o":"0/000.150.2200","d":"L/H INDICATOR","a":true,"c":""},{"o":"0/000.150.0410","d":"L/H FRONT INDICATOR","a":false,"c":""}]},{"r":"46","v":[{"o":"0/000.150.2100","d":"R/H INDICATOR","a":true,"c":""},{"o":"0/000.150.0420","d":"R/H REAR INDICATOR","a":false,"c":""}]},{"r":"47","v":[{"o":"0/000.150.0410","d":"L/H INDICATOR","a":false,"c":""}]},{"r":"48","v":[{"o":"0/000.150.0410","d":"L/H INDICATOR","a":false,"c":""}]},{"r":"49","v":[{"o":"0/000.150.0420","d":"R/H INDICATOR","a":false,"c":""}]}],"pins":{"39":[[75.88,17.46]],"12":[[79.78,16.53]],"37":[[17.1,19.56]],"29":[[90.09,20.95]],"36":[[21.86,36.32]],"35":[[28.18,35.27],[79.39,68.45]],"38":[[87.59,35.74]],"45":[[63.23,36.9],[43.09,78.46]],"30":[[35.75,38.18]],"32":[[15.07,47.73]],"46":[[87.28,48.89],[37.47,42.96]],"14":[[13.9,63.1]],"17":[[24.75,62.28]],"16":[[21.86,63.33]],"19":[[45.59,64.26]],"25":[[78.84,64.38]],"20":[[49.1,67.99]],"15":[[22.4,74.27]],"22":[[62.76,82.89]],"21":[[55.5,88.36]],"24":[[65.57,87.54]],"26":[[81.89,88.24]],"23":[[58.63,93.13]],"1":[[33.8,15.13]],"2":[[35.28,24.68]],"3":[[35.91,29.92]],"4":[[34.89,33.99]],"5":[[42.0,31.66]],"6":[[54.72,35.04]],"7":[[50.66,50.41],[64.72,46.57]],"8":[[55.27,56.0]],"9":[[58.86,41.33]],"10":[[77.28,37.37]],"11":[[72.44,21.42]],"13":[[82.98,13.5]],"18":[[27.71,64.38]],"27":[[86.1,50.87]],"31":[[8.9,33.06]],"33":[[83.76,71.83]],"34":[[28.02,31.08]]}},{"key":"exhaust-radiator-tank-airfilter","title":"Exhaust, cooling, tanks & airbox","refs":[{"r":"1","v":[{"o":"0/005.082.0272","d":"COMPLETE ENGINE","a":false,"c":""},{"o":"0/005.082.0318","d":"COMPLETE ENGINE","a":false,"c":""},{"o":"0/005.082.0312","d":"COMPLETE ENGINE (BAST.<=73599)","a":false,"c":""}]},{"r":"2","v":[{"o":"0/000.290.0514","d":"COMPLETE EXHAUST (BAST.>=73600)","a":true,"c":""},{"o":"0/000.290.0515","d":"COMPLETE EXHAUST","a":true,"c":""},{"o":"0/000.290.0503","d":"COMPLETE EXHAUST AUSTRIA","a":false,"c":""},{"o":"0/000.290.0512","d":"COMPLETE EXHAUST (BAST.<=73599)","a":true,"c":""}]},{"r":"3","v":[{"o":"0/000.260.0240","d":"EXHAUST CONNECTOR","a":true,"c":""},{"o":"0/000.680.0560","d":"EXHAUST JOINT CLAMP","a":true,"c":""},{"o":"0/000.680.0550","d":"CLAMPS","a":true,"c":""}]},{"r":"4","v":[{"o":"0/000.480.0711","d":"SPACER 14x8x5","a":true,"c":""},{"o":"0/000.360.0160","d":"EXHAUST/SILENCER GASKET","a":false,"c":""}]},{"r":"5","v":[{"o":"0/000.870.0102","d":"SILENCER","a":false,"c":""},{"o":"0/000.870.0101","d":"SILENCER","a":false,"c":""}]},{"r":"6","v":[{"o":"0/000.350.1000","d":"AIRBOX COVER","a":false,"c":""}]},{"r":"7","v":[{"o":"0/001.070.0050","d":"AIR FILTER","a":false,"c":""}]},{"r":"8","v":[{"o":"0/000.910.1021","d":"AIRBOX BODY","a":false,"c":""},{"o":"0/000.910.1010","d":"AIRBOX BODY (BAST.<=73599)","a":false,"c":""}]},{"r":"9","v":[{"o":"0/000.360.0610","d":"CARB-TO-AIRBOX RUBBER","a":true,"c":""},{"o":"0/000.360.0600","d":"CARB-TO-AIRBOX RUBBER","a":false,"c":""}]},{"r":"10","v":[{"o":"0/000.680.0150","d":"CARB RUBBER CLAMP","a":false,"c":""}]},{"r":"11","v":[{"o":"0/000.220.0560","d":"EXHAUST HEAT SHIELD","a":true,"c":""},{"o":"0/000.220.0562","d":"EXHAUST HEAT SHIELD","a":false,"c":""}]},{"r":"12","v":[{"o":"0/000.360.0460","d":"EXHAUST RUBBER MOUNT","a":false,"c":""}]},{"r":"13","v":[{"o":"0/000.470.6010","d":"FUEL LINE GUARD SPRING","a":false,"c":""}]},{"r":"14","v":[{"o":"0/005.460.1070B","d":"EXHAUST SPRING","a":true,"c":""}]},{"r":"15","v":[{"o":"0/000.290.0420","d":"FUEL LINE","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.680.0510","d":"FUEL LINE CLAMP","a":true,"c":""}]},{"r":"17","v":[{"o":"0/000.290.0420","d":"FUEL LINE","a":true,"c":""}]},{"r":"18","v":[{"o":"0/000.680.0520","d":"FUEL TAP CLAMP","a":true,"c":""}]},{"r":"19","v":[{"o":"0/000.680.0501","d":"CLIP","a":true,"c":""}]},{"r":"20","v":[{"o":"0/000.980.0572","d":"UPPER OIL TANK BRACKET","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.030.0550","d":"OIL TANK","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.410.0600","d":"OIL TANK CAP","a":true,"c":""}]},{"r":"23","v":[{"o":"0/000.410.0550","d":"OIL DIPSTICK/CAP","a":false,"c":""}]},{"r":"24","v":[{"o":"0/000.910.0200","d":"TOOL BOX","a":true,"c":""}]},{"r":"25","v":[{"o":"0/000.440.0129","d":"LOCK WASHER","a":true,"c":""}]},{"r":"26","v":[{"o":"0/000.220.0300","d":"R/H RADIATOR GUARD","a":true,"c":""}]},{"r":"27","v":[{"o":"0/000.220.0310","d":"L/H RADIATOR GUARD","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.680.0200","d":"CLAMP","a":true,"c":""}]},{"r":"29","v":[{"o":"0/000.460.1190B","d":"PLASTIC SCREW M6x12 (bag of 5)","a":false,"c":""}]},{"r":"30","v":[{"o":"0/000.640.0161","d":"RADIATOR HOSE SET","a":false,"c":""},{"o":"0/000.640.5100","d":"RADIATOR HOSE SET","a":false,"c":""}]},{"r":"31","v":[{"o":"0/000.360.0400","d":"RUBBER MOUNT","a":true,"c":""}]},{"r":"32","v":[{"o":"0/000.360.0710","d":"RUBBER SCREW HOUSING","a":false,"c":""}]},{"r":"33","v":[{"o":"0/000.580.5000","d":"RADIATOR","a":false,"c":""}]},{"r":"34","v":[{"o":"0/000.410.0150","d":"RADIATOR CAP","a":true,"c":""},{"o":"0/000.410.0151","d":"RADIATOR CAP (KTM type)","a":true,"c":""}]},{"r":"35","v":[{"o":"0/000.290.0630","d":"RADIATOR OVERFLOW PIPE 5x10","a":true,"c":""}]},{"r":"36","v":[{"o":"0/000.760.0020","d":"TOOL BAG","a":true,"c":""}]},{"r":"37","v":[{"o":"0/000.290.0630","d":"RADIATOR PIPE 5x10","a":true,"c":""}]},{"r":"38","v":[{"o":"0/000.410.0500","d":"FUEL TANK CAP","a":true,"c":""},{"o":"0/000.410.0502","d":"FUEL TANK CAP WITH KEY","a":true,"c":""}]},{"r":"39","v":[{"o":"0/000.360.0010","d":"RUBBER GASKET","a":true,"c":""}]},{"r":"40","v":[{"o":"0/000.030.0500","d":"FUEL TANK","a":false,"c":""}]},{"r":"41","v":[{"o":"0/000.290.0420","d":"FUEL LINE","a":true,"c":""}]},{"r":"42","v":[{"o":"0/000.370.0500","d":"FUEL TAP","a":true,"c":""}]},{"r":"43","v":[{"o":"0/000.460.0294B","d":"SCREW M5x50 (bag of 5)","a":true,"c":""}]},{"r":"44","v":[{"o":"0/005.430.5002","d":"KICK START LEVER","a":false,"c":""}]},{"r":"45","v":[{"o":"0/000.460.0311","d":"GEAR LEVER SCREW","a":true,"c":""}]},{"r":"46","v":[{"o":"0/005.430.0030","d":"GEAR PEDAL","a":false,"c":""}]},{"r":"47","v":[{"o":"0/000.460.0550B","d":"SCREW M8x100 (bag of 5)","a":false,"c":""}]},{"r":"48","v":[{"o":"0/000.460.1190B","d":"PLASTIC SCREW M6x12 (bag of 5)","a":false,"c":""}]},{"r":"50","v":[{"o":"0/000.680.0510","d":"FUEL LINE CLAMP","a":true,"c":""}]},{"r":"51","v":[{"o":"0/000.460.1189B","d":"PLASTIC SCREW M6x9 (bag of 5)","a":false,"c":""}]},{"r":"52","v":[{"o":"0/000.440.1210","d":"SPECIAL LOCK WASHER","a":true,"c":""}]},{"r":"53","v":[{"o":"0/000.440.1210","d":"SPECIAL LOCK WASHER","a":true,"c":""}]},{"r":"54","v":[{"o":"0/000.460.0740","d":"RADIATOR GUARD SCREW","a":true,"c":""}]},{"r":"55","v":[{"o":"0/000.460.0651","d":"SCREW M6x45","a":true,"c":""}]},{"r":"56","v":[{"o":"0/000.440.0160B","d":"WASHER 8.5x17 (bag of 5)","a":true,"c":""}]},{"r":"57","v":[{"o":"0/000.340.0180B","d":"NUT M8 (bag of 5)","a":true,"c":""}]},{"r":"58","v":[{"o":"0/000.460.0660B","d":"SCREW M8x20 (bag of 5)","a":true,"c":""}]},{"r":"59","v":[{"o":"0/000.360.0550","d":"SILENCER RUBBER MOUNT","a":false,"c":""}]},{"r":"60","v":[{"o":"0/000.440.0129","d":"LOCK WASHER","a":true,"c":""}]},{"r":"61","v":[{"o":"0/000.460.0150","d":"EXHAUST SHIELD SCREW (BAST.<=73599)","a":false,"c":""},{"o":"0/000.460.1189B","d":"PLASTIC SCREW M6x9 (bag of 5)","a":false,"c":""}]},{"r":"62","v":[{"o":"0/000.370.0440","d":"OIL TANK TAP","a":true,"c":""}]},{"r":"63","v":[{"o":"0/000.970.5050","d":"SPARK PLUG WRENCH","a":true,"c":""}]},{"r":"64","v":[{"o":"0/000.640.0000","d":"AIS VALVE","a":false,"c":""}]},{"r":"65","v":[{"o":"0/000.640.5101","d":"AIS VALVE PIPE SET","a":false,"c":""}]},{"r":"66","v":[{"o":"0/000.680.0534","d":"HOSE CLAMP 17/8","a":true,"c":""}]},{"r":"67","v":[{"o":"0/000.440.0131","d":"WASHER M6","a":true,"c":""}]},{"r":"68","v":[{"o":"0/000.460.0300B","d":"SCREW M6x16 (bag of 5)","a":true,"c":""}]},{"r":"69","v":[{"o":"0/000.660.2020","d":"EXHAUST PLATE","a":false,"c":""}]},{"r":"70","v":[{"o":"0/000.490.2000B","d":"EXHAUST GASKET 1.2mm","a":true,"c":""}]}],"pins":{"25":[[70.1,10.13]],"28":[[20.14,15.6]],"43":[[66.12,15.83]],"48":[[32.08,23.28],[10.3,68.68]],"29":[[51.21,22.82]],"70":[[57.22,26.78]],"10":[[72.21,28.64]],"56":[[84.39,28.52],[41.69,67.99],[71.35,87.08],[77.44,91.15]],"68":[[58.0,34.69]],"65":[[72.44,35.74]],"50":[[40.44,39.81],[51.83,43.54],[37.16,93.48]],"59":[[88.91,41.44]],"31":[[15.38,44.24]],"35":[[20.84,44.0]],"66":[[65.73,43.19],[61.05,49.83]],"36":[[34.5,46.57]],"41":[[49.34,46.92]],"64":[[61.98,46.92]],"42":[[40.75,49.94]],"17":[[48.48,59.02]],"53":[[10.38,60.65]],"22":[[37.31,63.8]],"26":[[22.48,68.68]],"61":[[62.45,73.81],[63.54,88.94]],"20":[[23.42,75.44]],"21":[[38.88,76.48]],"15":[[43.09,77.3]],"55":[[49.8,80.21]],"60":[[60.89,81.02]],"47":[[68.15,84.17]],"45":[[95.47,83.7]],"19":[[29.35,88.59]],"13":[[40.36,88.01],[41.37,58.79]],"14":[[43.56,88.94]],"11":[[59.09,91.5]],"57":[[80.56,93.13]],"62":[[33.1,94.88]],"46":[[92.82,93.71]],"1":[[69.01,73.34]],"2":[[49.96,94.53]],"3":[[72.44,54.37]],"4":[[68.46,46.33]],"5":[[77.28,39.23]],"6":[[68.93,18.98]],"7":[[76.5,11.76]],"8":[[80.87,14.09]],"9":[[76.11,29.1]],"12":[[44.65,67.87]],"16":[[48.17,53.2]],"18":[[45.75,40.51],[37.63,82.07]],"23":[[28.81,65.77]],"24":[[16.71,52.5]],"27":[[4.06,45.4]],"30":[[30.21,39.93]],"33":[[9.76,26.89]],"34":[[12.18,13.74]],"37":[[32.55,15.25]],"38":[[44.34,15.48]],"39":[[32.71,28.87]],"40":[[55.19,39.23]],"44":[[67.37,66.71]],"51":[[14.21,75.09],[24.67,78.35]],"52":[[24.59,82.89]],"58":[[91.1,36.44]],"63":[[25.29,47.61]],"67":[[61.05,34.69]],"69":[[61.12,28.29]]}},{"key":"motord","title":"Engine (Minarelli AM6)","refs":[{"r":"0","v":[{"o":"0/005.710.6871","d":"GASKET SET","a":true,"c":""},{"o":"0/000.640.9999","d":"TOOL SET","a":true,"c":""},{"o":"0/005.710.7266","d":"CUB SET","a":true,"c":""}]},{"r":"15","v":[{"o":"0/005.155.7141","d":"CARBURETOR PHBN 16 (SPIKE >= 73600)","a":false,"c":""},{"o":"0/005.155.7113","d":"CARBURETOR PHBN 12","a":false,"c":""}]},{"r":"19","v":[{"o":"0/005.718.2941","d":"CYLINDER WITH PISTON","a":false,"c":""}]},{"r":"62","v":[{"o":"0/005.711.2150","d":"PISTON COMPLETE 40.3","a":true,"c":""}]},{"r":"77","v":[{"o":"0/005.770.5252","d":"CYLINDER HEAD","a":true,"c":""}]},{"r":"82","v":[{"o":"0/005.822.2419","d":"STATOR","a":true,"c":""}]},{"r":"111","v":[{"o":"0/005.110.1559","d":"SPARK PLUG NGK BR9","a":true,"c":""}]},{"r":"212","v":[{"o":"0/005.210.7327","d":"CLUTCH COVER","a":false,"c":""}]},{"r":"213","v":[{"o":"0/005.210.7325","d":"OIL PUMP COVER","a":false,"c":""}]},{"r":"214","v":[{"o":"0/005.210.9114","d":"FLYWHEEL COVER","a":false,"c":""}]},{"r":"381","v":[{"o":"0/005.715.0780","d":"COMPLETE CRANKSHAFT","a":true,"c":""}]},{"r":"382","v":[{"o":"0/005.715.3464","d":"CONNECTING ROD 18","a":true,"c":""},{"o":"0/005.715.3377","d":"CONNECTING ROD 16","a":false,"c":""}]},{"r":"471","v":[{"o":"0/005.470.1067","d":"SPRING GUIDE CUP","a":true,"c":""}]},{"r":"582","v":[{"o":"0/005.580.4211","d":"FRONT SPROCKET Z11","a":false,"c":""}]},{"r":"606","v":[{"o":"0/005.601.4980","d":"INTAKE MANIFOLD 20","a":true,"c":""}]},{"r":"641","v":[{"o":"0/005.640.0196","d":"OIL PUMP PRICOL","a":true,"c":""},{"o":"0/005.640.1080","d":"OIL PUMP MIKUNI","a":false,"c":""}]},{"r":"681","v":[{"o":"0/005.714.2080","d":"PISTON RING SET 40.3x1.2","a":true,"c":""}]},{"r":"682","v":[{"o":"0/005.714.2080","d":"PISTON RING SET 40.3x1.2","a":true,"c":""}]},{"r":"701","v":[{"o":"0/005.703.7080","d":"CRANKCASE PAIR","a":false,"c":""}]},{"r":"911","v":[{"o":"0/005.010.3387","d":"PRIMARY SHAFT","a":false,"c":""}]},{"r":"912","v":[{"o":"0/005.010.1167","d":"KICKSTART SHAFT","a":true,"c":""}]},{"r":"914","v":[{"o":"0/005.010.4347","d":"SECONDARY SHAFT","a":true,"c":""}]},{"r":"915","v":[{"o":"0/005.010.5110","d":"SELECTOR SHAFT","a":true,"c":""}]},{"r":"917","v":[{"o":"0/005.010.9040","d":"COUNTERSHAFT","a":false,"c":""}]},{"r":"2430","v":[{"o":"0/005.240.5335","d":"OIL PUMP CABLE RUBBER","a":false,"c":""}]},{"r":"2478","v":[{"o":"0/005.240.5334","d":"OIL LINE RUBBER","a":true,"c":""}]},{"r":"2607","v":[{"o":"0/005.260.5535","d":"HOSE","a":true,"c":""}]},{"r":"2655","v":[{"o":"0/005.260.6057","d":"TUBE SUPPORT","a":true,"c":""}]},{"r":"4001","v":[{"o":"0/005.710.2527","d":"CLUTCH BASKET WITH GEAR 20/71","a":true,"c":""}]},{"r":"4045","v":[{"o":"0/005.400.5290","d":"KICKSTART GEAR Z21","a":false,"c":""}]},{"r":"4060","v":[{"o":"0/005.400.5673","d":"COUNTERSHAFT GEAR Z34","a":false,"c":""}]},{"r":"4066","v":[{"o":"0/005.400.6547","d":"KICKSTART PINION","a":true,"c":""}]},{"r":"4625","v":[{"o":"0/005.460.1407","d":"KICKSTART PINION SPRING","a":false,"c":""}]},{"r":"4665","v":[{"o":"0/005.460.2297","d":"KICKSTART RETURN SPRING","a":false,"c":""}]},{"r":"5303","v":[{"o":"0/005.530.1047B","d":"CIRCLIP (bag of 5)","a":true,"c":""}]},{"r":"5310","v":[{"o":"0/005.530.1759B","d":"O-RING 25.07x2.62 VITON (bag of 5)","a":true,"c":""}]},{"r":"5315","v":[{"o":"0/005.530.1788","d":"RETAINING RING 15.1x2.7","a":true,"c":""}]},{"r":"5330","v":[{"o":"0/005.530.8860","d":"FLYWHEEL GASKET","a":true,"c":""}]},{"r":"6015","v":[{"o":"0/005.600.0871","d":"REED VALVE","a":true,"c":""}]},{"r":"9323","v":[{"o":"0/005.030.2130","d":"PISTON PIN CLIP","a":true,"c":""}]},{"r":"9351","v":[{"o":"0/005.030.3016","d":"CIRCLIP 8","a":true,"c":""}]},{"r":"9352","v":[{"o":"0/005.030.3026","d":"CIRCLIP 12","a":true,"c":""}]},{"r":"9355","v":[{"o":"0/005.030.3057","d":"CIRCLIP 18","a":true,"c":""}]},{"r":"9356","v":[{"o":"0/005.030.3066","d":"CIRCLIP 18 KICKSTART","a":true,"c":""}]},{"r":"9362","v":[{"o":"0/005.030.3127","d":"CIRCLIP 20","a":true,"c":""}]},{"r":"9363","v":[{"o":"0/005.030.3139","d":"CIRCLIP 14","a":true,"c":""}]},{"r":"9365","v":[{"o":"0/005.030.3147","d":"CIRCLIP 20","a":true,"c":""}]},{"r":"24107","v":[{"o":"0/005.240.3750","d":"CARB OVERFLOW TUBE","a":false,"c":""}]},{"r":"49001","v":[{"o":"0/005.490.0439","d":"STARTER MOTOR BRACKET","a":true,"c":""}]},{"r":"49005","v":[{"o":"0/005.490.0190","d":"FIXING BAND","a":false,"c":""}]},{"r":"49022","v":[{"o":"0/005.490.0499","d":"FIXING BAND 8 BLACK","a":false,"c":""}]},{"r":"49070","v":[{"o":"0/005.490.4663","d":"SPACER 16.9x24x8","a":true,"c":""}]},{"r":"49099","v":[{"o":"0/005.490.3662","d":"NUT M5x0.75","a":true,"c":""}]},{"r":"49131","v":[{"o":"0/005.490.6514","d":"WASHER 16x25x0.4","a":true,"c":""}]},{"r":"49135","v":[{"o":"0/005.490.6556","d":"WASHER 20.45x28x0.6","a":true,"c":""}]},{"r":"49193","v":[{"o":"0/005.490.7010","d":"WASHER 14x26x0.5","a":true,"c":""}]},{"r":"49244","v":[{"o":"0/000.460.0310","d":"SCREW M6x22","a":true,"c":""}]},{"r":"49255","v":[{"o":"0/005.491.9059","d":"SCREW M6x12","a":true,"c":""}]},{"r":"49256","v":[{"o":"0/005.490.8599","d":"SCREW M5x12","a":true,"c":""}]},{"r":"49292","v":[{"o":"0/005.491.9200","d":"SCREW M6x20","a":false,"c":""}]},{"r":"49405","v":[{"o":"0/005.491.7570","d":"WASHER 6x11x1.2","a":true,"c":""}]},{"r":"49514","v":[{"o":"0/005.491.9631","d":"SCREW M6x25","a":true,"c":""}]},{"r":"49528","v":[{"o":"0/005.491.8071","d":"DISCONTINUED","a":false,"c":""}]},{"r":"53309","v":[{"o":"0/005.530.1749B","d":"O-RING 2.62 (bag of 5)","a":true,"c":""}]},{"r":"53310","v":[{"o":"0/005.530.1739B","d":"O-RING 2.62 (bag of 5)","a":true,"c":""}]}],"pins":{"15":[[21.94,21.65]],"19":[[52.07,21.65]],"62":[[61.9,36.67]],"77":[[53.47,15.37]],"82":[[10.77,24.56]],"111":[[53.55,12.57]],"212":[[79.16,86.38]],"213":[[89.38,46.22]],"214":[[1.01,35.16]],"381":[[38.8,35.74]],"382":[[48.56,12.22]],"471":[[65.18,90.22]],"582":[[18.81,53.32]],"606":[[29.66,20.02]],"641":[[83.06,44.35]],"681":[[61.9,33.29]],"682":[[62.06,34.81]],"701":[[25.21,32.13],[50.74,47.5]],"911":[[34.5,54.95]],"912":[[70.18,90.69]],"914":[[25.76,66.71]],"915":[[14.21,71.25]],"917":[[33.02,40.51]],"2430":[[85.95,48.31]],"2478":[[93.52,38.18]],"4001":[[66.9,52.15],[52.15,66.94]],"4045":[[58.24,86.61]],"4060":[[68.31,42.72]],"4066":[[58.39,95.81]],"4625":[[60.27,88.13]],"4665":[[64.17,99.65]],"5303":[[45.04,27.47]],"5310":[[96.8,32.13]],"5315":[[79.78,66.24]],"5330":[[6.87,22.47]],"6015":[[32.94,19.79]],"9323":[[53.32,37.6]],"9351":[[79.39,38.18],[74.32,56.58],[72.83,65.89]],"9352":[[7.42,65.77],[19.44,71.59]],"9355":[[33.72,61.23]],"9356":[[61.75,97.79]],"9362":[[16.32,63.45],[24.12,56.58]],"9363":[[40.2,75.2]],"24107":[[31.38,10.94]],"49005":[[25.14,22.12]],"49022":[[27.01,15.13]],"49070":[[44.81,39.12]],"49131":[[72.29,92.2]],"49135":[[23.73,65.54],[33.72,70.2]],"49244":[[27.17,17.0]],"49255":[[41.45,42.26],[99.06,73.92]],"49292":[[29.12,16.41]],"53309":[[65.42,22.12]],"53310":[[65.34,24.21]]}},{"key":"plastics","title":"Bodywork & seat","refs":[{"r":"1","v":[{"o":"0/000.620.1600","d":"HEADLIGHT COWL BLUE 2005","a":false,"c":"C"},{"o":"0/000.620.1602","d":"HEADLIGHT COWL RED 2005","a":true,"c":"A"},{"o":"0/000.620.5201","d":"HEADLIGHT COWL BLACK 2005","a":false,"c":"D"},{"o":"0/000.620.2500","d":"HEADLIGHT COWL BLUE (older)","a":true,"c":"D"},{"o":"0/000.620.2501","d":"HEADLIGHT COWL BLUE 2002","a":true,"c":"K"}]},{"r":"2","v":[{"o":"0/000.360.0171","d":"RUBBER HEADLIGHT BRACKET","a":false,"c":""},{"o":"0/000.360.0173","d":"RUBBER HEADLIGHT LOCK BRACKET","a":false,"c":""}]},{"r":"3","v":[{"o":"0/000.570.2612","d":"R/H RADIATOR PANEL BLUE","a":true,"c":"C"},{"o":"0/000.570.2620","d":"R/H RADIATOR PANEL RED","a":true,"c":"A"},{"o":"0/000.570.2616","d":"R/H RADIATOR PANEL BLACK","a":false,"c":"B"},{"o":"0/000.570.2500","d":"R/H RADIATOR PANEL BLUE (older)","a":true,"c":"D"}]},{"r":"4","v":[{"o":"0/000.050.2100","d":"FRONT MUDGUARD","a":false,"c":"C"},{"o":"0/000.050.0522","d":"FRONT MUDGUARD","a":false,"c":"A"},{"o":"0/000.050.2500","d":"FRONT MUDGUARD BLUE (older)","a":true,"c":"D"},{"o":"0/000.050.0530","d":"FRONT MUDGUARD GREY","a":true,"c":"E"}]},{"r":"5","v":[{"o":"0/000.220.0540","d":"FORK GUARDS (pair)","a":false,"c":""},{"o":"0/000.220.5001","d":"FORK GUARD","a":false,"c":""}]},{"r":"6","v":[{"o":"0/000.570.2613","d":"L/H RADIATOR PANEL BLUE","a":false,"c":"C"},{"o":"0/000.570.2621","d":"L/H RADIATOR PANEL RED","a":true,"c":"A"},{"o":"0/000.570.2617","d":"L/H RADIATOR PANEL BLACK","a":false,"c":"B"},{"o":"0/000.570.2510","d":"L/H RADIATOR PANEL BLUE (older)","a":true,"c":"D"}]},{"r":"7","v":[{"o":"0/000.570.2602","d":"R/H SIDE PANEL BLUE","a":false,"c":"C"},{"o":"0/000.570.2610","d":"R/H SIDE PANEL RED","a":true,"c":"A"},{"o":"0/000.570.2606","d":"R/H SIDE PANEL BLACK","a":true,"c":"B"},{"o":"0/000.570.2520","d":"R/H SIDE PANEL BLUE (older)","a":true,"c":"D"}]},{"r":"8","v":[{"o":"0/000.350.5510","d":"HEADLIGHT COVER BLUE","a":true,"c":"C"},{"o":"0/000.350.5517","d":"HEADLIGHT COVER BLACK","a":true,"c":"B"},{"o":"0/000.460.0600B","d":"SCREW M6x16","a":true,"c":""}]},{"r":"9","v":[{"o":"0/000.040.0550","d":"COMPLETE SEAT GREY","a":true,"c":"C"},{"o":"0/000.040.2200","d":"SEAT","a":true,"c":""},{"o":"0/000.040.2300","d":"SEAT","a":true,"c":"D"},{"o":"0/000.040.2140","d":"COMPLETE SEAT BLUE","a":true,"c":"D"}]},{"r":"10","v":[{"o":"0/000.570.2603","d":"L/H SIDE PANEL BLUE","a":false,"c":"C"},{"o":"0/000.570.2611","d":"L/H SIDE PANEL RED","a":true,"c":"A"},{"o":"0/000.570.2607","d":"L/H SIDE PANEL BLACK","a":false,"c":"B"},{"o":"0/000.570.2530","d":"L/H SIDE PANEL BLUE (older)","a":false,"c":"D"}]},{"r":"11","v":[{"o":"0/000.060.2600","d":"REAR MUDGUARD BLUE","a":false,"c":"C"},{"o":"0/000.060.2630","d":"REAR MUDGUARD RED","a":false,"c":"A"},{"o":"0/000.060.2660","d":"REAR MUDGUARD BLACK","a":false,"c":"B"},{"o":"0/000.060.2500","d":"REAR MUDGUARD BLUE (older)","a":true,"c":"D"}]},{"r":"12","v":[{"o":"0/000.220.0510","d":"FRONT SPROCKET COVER","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.200.0311","d":"SHOCK COVER","a":true,"c":""},{"o":"0/000.200.0310","d":"SHOCK COVER","a":false,"c":""}]},{"r":"14","v":[{"o":"0/000.200.0301","d":"CHAIN GUARD","a":true,"c":""},{"o":"0/000.200.0300","d":"CHAIN COVER","a":false,"c":""}]},{"r":"15","v":[{"o":"0/000.470.0510","d":"BRAKE LINE GUIDE SPRING","a":true,"c":""},{"o":"0/000.360.0172","d":"RUBBER LIGHT SUPPORT","a":true,"c":""},{"o":"0/000.470.0520","d":"SPEEDO CABLE GUIDE SPRING","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.470.0510","d":"BRAKE LINE GUIDE SPRING","a":true,"c":""},{"o":"0/000.470.0520","d":"SPEEDO CABLE GUIDE SPRING","a":true,"c":""}]},{"r":"17","v":[{"o":"0/000.480.0450","d":"MUDGUARD SPACER","a":true,"c":""}]},{"r":"18","v":[{"o":"0/000.480.1160","d":"FORK CAP SPACER","a":true,"c":""}]},{"r":"19","v":[{"o":"0/000.460.1189B","d":"PLASTIC SCREW M6x9 (bag of 5)","a":false,"c":""}]},{"r":"20","v":[{"o":"0/000.460.1180B","d":"PLASTIC SCREW M6x17.5 (bag of 5)","a":false,"c":""}]},{"r":"21","v":[{"o":"0/000.460.2000B","d":"SCREW M5x10 (bag of 5)","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.440.0130B","d":"WASHER 6.5x17x1.5 (bag of 5)","a":true,"c":""}]},{"r":"23","v":[{"o":"0/000.460.0610B","d":"SCREW M6x20 (bag of 5)","a":true,"c":""}]},{"r":"24","v":[{"o":"0/000.460.0421","d":"FRONT BRAKE LEVER BOLT","a":true,"c":""},{"o":"0/000.460.0620B","d":"SCREW M6x25 (bag of 5)","a":true,"c":""}]},{"r":"25","v":[{"o":"0/000.440.0129","d":"LOCK WASHER","a":true,"c":""}]},{"r":"26","v":[{"o":"0/000.460.0291B","d":"SCREW M5x25 (bag of 5)","a":true,"c":""},{"o":"0/000.460.0280","d":"SCREW M5x12","a":true,"c":""}]},{"r":"27","v":[{"o":"0/000.340.0172","d":"NUT M5","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.340.0170B","d":"NUT M6 (bag of 5)","a":true,"c":""}]},{"r":"29","v":[{"o":"0/000.460.0322","d":"SCREW WITH SPACER","a":true,"c":""}]},{"r":"30","v":[{"o":"0/000.460.0294B","d":"SCREW M5x50 (bag of 5)","a":true,"c":""}]},{"r":"31","v":[{"o":"0/000.060.1020","d":"REAR MUDGUARD RR SPIKE 2005 (number plate holder)","a":false,"c":""},{"o":"0/000.060.1010","d":"TAIL MUDGUARD","a":false,"c":""}]},{"r":"32","v":[{"o":"0/000.440.5000B","d":"PLASTIC WASHER M6","a":true,"c":""}]},{"r":"33","v":[{"o":"0/000.220.0570","d":"BASH PLATE","a":true,"c":""},{"o":"0/000.460.1182B","d":"PLASTIC SCREW M6x15 (bag of 5)","a":true,"c":""}]},{"r":"34","v":[{"o":"0/000.680.0070","d":"CABLE TIE","a":true,"c":""},{"o":"0/000.680.0150","d":"CARB RUBBER CLAMP","a":false,"c":""}]},{"r":"35","v":[{"o":"0/000.980.0511","d":"HEADLIGHT BRACKET","a":true,"c":""},{"o":"0/000.360.0173","d":"RUBBER HEADLIGHT LOCK BRACKET","a":false,"c":""}]},{"r":"36","v":[{"o":"0/000.980.0511","d":"HEADLIGHT BRACKET","a":true,"c":""}]},{"r":"37","v":[{"o":"0/000.350.0500","d":"DISCONTINUED","a":false,"c":""}]},{"r":"38","v":[{"o":"0/000.300.0550","d":"DISCONTINUED","a":false,"c":""}]},{"r":"39","v":[{"o":"0/000.460.1165B","d":"SELF-TAPPING SCREW 3.5x12 (bag of 5)","a":true,"c":""}]},{"r":"40","v":[{"o":"0/000.460.1180B","d":"PLASTIC SCREW M6x17.5 (bag of 5)","a":false,"c":""}]},{"r":"41","v":[{"o":"0/000.460.0300B","d":"SCREW M6x16 (bag of 5)","a":true,"c":""}]},{"r":"42","v":[{"o":"0/000.440.5001","d":"FIBRE WASHER","a":true,"c":""}]},{"r":"43","v":[{"o":"0/000.680.5020","d":"CABLE GUIDE","a":false,"c":""}]},{"r":"44","v":[{"o":"0/000.620.5201","d":"HEADLIGHT COWL BLACK 2005","a":false,"c":""},{"o":"0/000.220.0540","d":"FORK GUARDS (pair)","a":false,"c":""}]},{"r":"45","v":[{"o":"0/000.460.1183","d":"SCREW","a":false,"c":""}]}],"pins":{"20":[[77.91,13.04]],"11":[[67.6,14.78]],"19":[[39.97,20.49],[77.36,35.16],[67.53,54.48]],"28":[[73.85,27.94],[29.35,32.36],[57.92,51.34],[69.09,61.58]],"10":[[78.22,47.61]],"21":[[48.79,51.46],[29.51,40.86]],"14":[[84.54,53.32]],"17":[[33.33,56.46]],"22":[[31.15,63.56],[68.31,68.45],[20.53,89.41]],"15":[[25.53,67.17]],"24":[[32.24,70.55]],"23":[[30.91,72.88]],"13":[[61.51,73.81]],"30":[[32.47,80.33]],"26":[[57.38,81.26]],"25":[[51.44,85.91],[31.54,76.37]],"42":[[15.46,92.2]],"33":[[33.33,92.43]],"34":[[43.87,94.99]],"41":[[17.25,96.97]],"1":[[12.26,28.75]],"3":[[16.0,46.22]],"4":[[10.15,76.02]],"5":[[18.74,80.33]],"6":[[38.33,49.01]],"7":[[46.37,25.61]],"8":[[71.51,68.92]],"9":[[54.64,29.8]],"12":[[48.24,81.14]],"16":[[23.42,75.9]],"18":[[25.68,94.76]],"27":[[22.4,72.99]],"29":[[17.1,93.36]],"31":[[87.28,20.95]],"32":[[50.43,15.25]],"35":[[35.44,15.48]],"36":[[31.93,21.42]],"43":[[2.5,84.63]],"44":[[28.88,83.7]],"45":[[33.57,33.29]]}},{"key":"suspension","title":"Fork, steering & rear shock","refs":[{"r":"0","v":[{"o":"0/000.130.0530","d":"COMPLETE FORK 2005","a":false,"c":""},{"o":"0/000.130.0540","d":"COMPLETE FORK","a":true,"c":""},{"o":"0/000.130.0520","d":"COMPLETE FORK (BAST.>=74239)","a":false,"c":""},{"o":"0/000.130.0510","d":"COMPLETE FORK (BAST.<=74238)","a":false,"c":""}]},{"r":"1","v":[{"o":"0/000.460.0345B","d":"SCREW M8x25 (bag of 5)","a":true,"c":""}]},{"r":"2","v":[{"o":"0/000.440.0180","d":"WASHER","a":true,"c":""}]},{"r":"3","v":[{"o":"0/000.460.0480","d":"SCREW M8x50","a":true,"c":""}]},{"r":"4","v":[{"o":"0/000.340.0180B","d":"NUT M8 (bag of 5)","a":true,"c":""}]},{"r":"5","v":[{"o":"0/000.880.0522","d":"RIGHT FORK LEG CAP","a":true,"c":""},{"o":"0/000.880.0511","d":"RIGHT FORK CAP","a":true,"c":""},{"o":"0/000.880.0530","d":"RIGHT FORK LEG CAP","a":true,"c":""}]},{"r":"6","v":[{"o":"0/000.880.0523","d":"LEFT FORK CAP","a":true,"c":""},{"o":"0/000.880.0521","d":"LEFT FORK CAP","a":true,"c":""},{"o":"0/000.880.0540","d":"LEFT FORK LEG CAP","a":true,"c":""}]},{"r":"7","v":[{"o":"0/000.710.0500","d":"FORK SPRING GUIDE","a":true,"c":""},{"o":"0/000.490.0520","d":"FORK CAP O-RING","a":true,"c":""}]},{"r":"8","v":[{"o":"0/000.410.0520","d":"LOWER FORK CAP","a":true,"c":""},{"o":"0/000.440.1200","d":"FORK SPRING WASHER","a":true,"c":""}]},{"r":"9","v":[{"o":"0/000.290.0520","d":"FORK BUMP STOP","a":true,"c":""},{"o":"0/000.460.0342","d":"SPRING STOP SCREW","a":true,"c":""}]},{"r":"10","v":[{"o":"0/000.470.0720","d":"FORK MAIN SPRING","a":false,"c":""},{"o":"0/000.470.0750","d":"FORK REBOUND SPRING","a":false,"c":""}]},{"r":"11","v":[{"o":"0/000.290.0551","d":"RIGHT FORK TUBE","a":true,"c":""},{"o":"0/000.290.0550","d":"RIGHT FORK TUBE","a":true,"c":""},{"o":"0/000.290.0531","d":"FORK TUBE","a":true,"c":""}]},{"r":"12","v":[{"o":"0/000.460.0320","d":"SCREW M8x30","a":true,"c":""},{"o":"0/000.440.1200","d":"FORK SPRING WASHER","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.460.0342","d":"SPRING STOP SCREW","a":true,"c":""}]},{"r":"14","v":[{"o":"0/000.710.0500","d":"FORK SPRING GUIDE","a":true,"c":""}]},{"r":"15","v":[{"o":"0/000.880.0501","d":"FORK LEG L/R","a":true,"c":""},{"o":"0/000.880.0500","d":"FORK LEG","a":true,"c":""},{"o":"0/000.880.0502","d":"FORK LEG","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.410.0210","d":"UPPER FORK CAP","a":false,"c":""}]},{"r":"17","v":[{"o":"0/000.410.0510","d":"THREADED FORK CAP","a":true,"c":""}]},{"r":"18","v":[{"o":"0/000.490.0510","d":"UPPER FORK O-RING","a":false,"c":""}]},{"r":"19","v":[{"o":"0/000.490.0500","d":"FORK O-RING","a":false,"c":""}]},{"r":"20","v":[{"o":"0/000.300.0500","d":"FORK CIRCLIP","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.290.0510","d":"FORK HEAD TUBE","a":true,"c":""},{"o":"0/000.290.0511","d":"FORK HEAD TUBE","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.980.0530","d":"FORK SEAL BRACKET","a":false,"c":""}]},{"r":"23","v":[{"o":"0/000.890.0500","d":"FORK OIL SEAL","a":true,"c":""}]},{"r":"24","v":[{"o":"0/000.300.0510","d":"FORK SEAL CIRCLIPS","a":false,"c":""}]},{"r":"25","v":[{"o":"0/000.890.0510","d":"FORK DUST SEAL","a":false,"c":""}]},{"r":"26","v":[{"o":"0/000.660.0670","d":"LOWER TRIPLE CLAMP WITH STEM","a":true,"c":""},{"o":"0/000.660.0663","d":"LOWER TRIPLE CLAMP","a":true,"c":""},{"o":"0/000.660.0661","d":"LOWER TRIPLE CLAMP (BAST.>=74239)","a":true,"c":""},{"o":"0/000.660.0660","d":"LOWER TRIPLE CLAMP (BAST.<=74238)","a":true,"c":""}]},{"r":"27","v":[{"o":"0/000.460.1602","d":"STEERING STOP SCREW","a":true,"c":""},{"o":"0/000.460.1601B","d":"STEERING STOP SCREW (bag of 5)","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.660.0680","d":"UPPER TRIPLE CLAMP","a":true,"c":""},{"o":"0/000.660.0662","d":"UPPER TRIPLE CLAMP","a":true,"c":""},{"o":"0/000.660.0650","d":"UPPER TRIPLE CLAMP","a":true,"c":""}]},{"r":"29","v":[{"o":"0/000.960.0100","d":"STEERING BEARING","a":true,"c":""},{"o":"0/000.960.0200","d":"STEERING BEARING","a":true,"c":""},{"o":"0/000.960.0040","d":"STEERING BEARING SET","a":false,"c":""}]},{"r":"30","v":[{"o":"0/000.200.7001","d":"COVER","a":false,"c":""}]},{"r":"31","v":[{"o":"0/000.200.7000","d":"STEERING DUST COVER","a":false,"c":""}]},{"r":"32","v":[{"o":"0/000.340.0550","d":"LOWER STEERING NUT","a":false,"c":""},{"o":"0/000.340.0620","d":"STEERING NUT (BAST.<=74238)","a":true,"c":""}]},{"r":"33","v":[{"o":"0/000.440.0050B","d":"STEERING NUT WASHER (bag of 5)","a":false,"c":""}]},{"r":"34","v":[{"o":"0/000.340.0325","d":"UPPER STEERING NUT","a":false,"c":""},{"o":"0/000.340.0320B","d":"STEERING STEM CAP NUT (bag of 5)","a":false,"c":""}]},{"r":"35","v":[{"o":"0/000.980.0630","d":"GUIDE","a":true,"c":""},{"o":"0/000.290.0540","d":"LEFT FORK TUBE","a":true,"c":""},{"o":"0/000.290.0541","d":"LEFT FORK TUBE","a":true,"c":""}]},{"r":"36","v":[{"o":"0/000.460.0320","d":"SCREW M8x30","a":true,"c":""},{"o":"0/000.460.0353","d":"SCREW M8x45","a":false,"c":""}]},{"r":"37","v":[{"o":"0/000.440.0180","d":"WASHER","a":true,"c":""}]},{"r":"38","v":[{"o":"0/000.460.0470","d":"SCREW M8x40","a":true,"c":""}]},{"r":"39","v":[{"o":"0/000.290.0530","d":"LEFT FORK VALVE TUBE","a":true,"c":""}]},{"r":"40","v":[{"o":"0/000.360.0370","d":"SWING ARM BUSH","a":true,"c":""}]},{"r":"41","v":[{"o":"0/000.460.0402B","d":"SCREW M10x50 (bag of 5)","a":true,"c":""},{"o":"0/000.340.0182B","d":"NUT M10 (bag of 5)","a":true,"c":""}]},{"r":"42","v":[{"o":"0/000.440.0110B","d":"WASHER 10x24x1.5 (bag of 5)","a":true,"c":""}]},{"r":"43","v":[{"o":"0/000.340.0182B","d":"NUT M10 (bag of 5)","a":true,"c":""},{"o":"0/000.460.0402B","d":"SCREW M10x50 (bag of 5)","a":true,"c":""}]},{"r":"44","v":[{"o":"0/000.180.0711","d":"SPIKE REAR SHOCK ABSORBER","a":false,"c":""},{"o":"0/000.180.5000","d":"REAR SHOCK ABSORBER","a":false,"c":""}]},{"r":"45","v":[{"o":"0/000.360.0370","d":"SWING ARM BUSH","a":true,"c":""},{"o":"0/000.350.0202","d":"HANDLEBAR CLAMP COVER","a":false,"c":""}]},{"r":"46","v":[{"o":"0/000.640.0360","d":"LINKAGE SET RIGHT","a":true,"c":""},{"o":"0/000.480.1550","d":"SLEEVE","a":true,"c":""}]},{"r":"47","v":[{"o":"0/000.640.0361","d":"LINKAGE SET LEFT","a":true,"c":""},{"o":"0/000.460.0407","d":"SCREW M10x75","a":true,"c":""}]},{"r":"48","v":[{"o":"0/000.770.0103","d":"COMPLETE LINKAGE 2007","a":false,"c":""}]}],"pins":{"38":[[61.83,12.57]],"54":[[82.28,18.39]],"28":[[54.25,23.63]],"17":[[37.7,26.31]],"13":[[25.45,30.5],[45.9,35.16]],"33":[[63.47,30.85]],"12":[[23.65,33.76],[46.37,38.88]],"11":[[21.47,46.33]],"19":[[32.55,52.39]],"42":[[73.15,54.48],[77.75,56.11],[83.45,84.98]],"20":[[31.85,55.65]],"35":[[43.17,56.69]],"21":[[31.23,58.44]],"41":[[69.4,61.0],[71.74,82.54]],"27":[[51.6,61.35]],"10":[[17.95,63.1]],"23":[[30.29,64.03]],"36":[[46.21,63.8]],"24":[[29.59,66.36]],"40":[[40.12,71.71]],"51":[[72.76,71.59]],"44":[[81.03,77.65]],"39":[[39.03,79.39]],"47":[[89.85,81.72]],"37":[[20.3,84.75]],"48":[[72.91,91.73]],"49":[[75.96,92.67]],"1":[[14.99,88.71]],"2":[[13.51,85.33]],"3":[[13.04,79.05]],"4":[[22.48,80.68]],"5":[[15.46,76.37]],"6":[[38.8,88.01]],"7":[[21.78,75.79]],"8":[[16.55,71.01]],"9":[[22.87,69.85]],"14":[[45.75,42.03]],"15":[[34.58,41.33]],"16":[[38.33,23.4]],"18":[[37.0,29.34]],"22":[[30.05,61.23]],"25":[[28.73,68.92]],"26":[[56.52,73.92]],"29":[[55.66,37.95]],"30":[[52.62,56.23]],"31":[[62.53,36.32]],"32":[[57.06,32.13]],"34":[[57.38,27.12]],"43":[[83.92,60.54]],"45":[[83.61,79.05]],"46":[[85.95,80.21]],"50":[[77.91,94.06]],"52":[[55.58,17.11]]}},{"key":"wheels","title":"Wheels, brakes & chain","refs":[{"r":"0","v":[{"o":"0/000.110.0300","d":"FRONT WHEEL COMPLETE","a":false,"c":""},{"o":"0/000.120.0300","d":"REAR WHEEL COMPLETE","a":false,"c":""},{"o":"0/000.820.0560","d":"COMPLETE BRAKE SET","a":true,"c":""},{"o":"0/000.820.0550","d":"COMPLETE BRAKE SET","a":true,"c":""}]},{"r":"1","v":[{"o":"0/000.190.0710","d":"FRONT AXLE","a":true,"c":""}]},{"r":"2","v":[{"o":"0/000.440.0123B","d":"WASHER 15 (bag of 5)","a":true,"c":""}]},{"r":"3","v":[{"o":"0/001.010.0110","d":"SPIKE SPEEDO DRIVE","a":false,"c":""}]},{"r":"4","v":[{"o":"0/000.480.1500","d":"FRONT SPACER","a":false,"c":""}]},{"r":"5","v":[{"o":"0/000.550.0501","d":"SPEEDOMETER CABLE","a":false,"c":""},{"o":"0/000.550.0500","d":"SPEEDOMETER CABLE","a":false,"c":""}]},{"r":"6","v":[{"o":"0/000.510.0551","d":"SPIKE FRONT TYRE","a":false,"c":""},{"o":"0/000.510.0550","d":"FRONT TYRE 80/90-21 (enduro)","a":true,"c":""}]},{"r":"7","v":[{"o":"0/000.230.0520","d":"FRONT SPOKES","a":false,"c":""}]},{"r":"8","v":[{"o":"0/000.540.0550","d":"FRONT RIM","a":true,"c":""}]},{"r":"9","v":[{"o":"0/000.070.0092","d":"FRONT HUB","a":true,"c":""},{"o":"0/000.070.0090","d":"FRONT HUB ONLY","a":false,"c":""}]},{"r":"10","v":[{"o":"0/000.520.0540","d":"INNER TUBE 21in (enduro)","a":true,"c":""}]},{"r":"11","v":[{"o":"0/000.220.0090","d":"RIM TAPE","a":false,"c":""}]},{"r":"12","v":[{"o":"0/000.960.0050","d":"WHEEL BEARING 6202","a":true,"c":""}]},{"r":"13","v":[{"o":"0/000.480.0240","d":"FRONT WHEEL BEARING SPACER","a":true,"c":""}]},{"r":"14","v":[{"o":"0/000.360.0410","d":"RUBBER BEARING SPACER","a":false,"c":""}]},{"r":"15","v":[{"o":"0/000.480.1510","d":"LEFT FRONT WHEEL SPACER","a":true,"c":""}]},{"r":"16","v":[{"o":"0/000.340.0300B","d":"AXLE NUT (bag of 5)","a":true,"c":""}]},{"r":"17","v":[{"o":"0/000.930.0060","d":"BRAKE PADS","a":true,"c":""},{"o":"0/000.640.0461","d":"REAR BRAKE PADS (FRAME>552371)","a":false,"c":""},{"o":"0/000.640.0420","d":"REAR CALIPER REPAIR KIT","a":false,"c":""}]},{"r":"18","v":[{"o":"0/000.860.0050","d":"FLOATING FRONT CALIPER","a":false,"c":""},{"o":"0/000.860.0015","d":"FRONT CALIPER GOLD RR/SPIKE 2006","a":true,"c":""},{"o":"0/000.860.0012","d":"SINGLE REAR CALIPER","a":true,"c":""}]},{"r":"19","v":[{"o":"0/000.290.0247","d":"FRONT BRAKE HOSE","a":true,"c":""},{"o":"0/000.290.0255","d":"FRONT BRAKE HOSE (FRAME>552371)","a":true,"c":""}]},{"r":"20","v":[{"o":"0/000.460.1300B","d":"BANJO BOLT (bag of 5)","a":true,"c":""}]},{"r":"21","v":[{"o":"0/000.840.0090","d":"BRAKE PEDAL CLEVIS","a":true,"c":""}]},{"r":"22","v":[{"o":"0/000.840.0060","d":"SECURING PIN","a":true,"c":""}]},{"r":"23","v":[{"o":"0/000.130.0150","d":"BRAKE PEDAL FORK","a":false,"c":""}]},{"r":"24","v":[{"o":"0/000.340.0130B","d":"NUT M6 (bag of 5)","a":true,"c":""}]},{"r":"25","v":[{"o":"0/000.240.0270","d":"BRAKE ROD","a":false,"c":""}]},{"r":"26","v":[{"o":"0/000.820.0306","d":"REAR BRAKE MASTER CYLINDER","a":false,"c":""},{"o":"0/000.820.0314","d":"REAR BRAKE MASTER CYLINDER (FRAME>552371)","a":false,"c":""}]},{"r":"27","v":[{"o":"0/000.680.0500","d":"BRAKE HOSE CLAMP","a":true,"c":""}]},{"r":"28","v":[{"o":"0/000.290.0590","d":"REAR BRAKE HOSE","a":true,"c":""},{"o":"0/000.290.0248","d":"REAR BRAKE HOSE","a":false,"c":""},{"o":"0/000.290.0246","d":"REAR BRAKE HOSE","a":false,"c":""},{"o":"0/000.290.0256","d":"REAR BRAKE HOSE (FRAME>552371)","a":false,"c":""}]},{"r":"29","v":[{"o":"0/000.720.0073","d":"REAR BRAKE STOP SWITCH","a":true,"c":""}]},{"r":"30","v":[{"o":"0/000.030.0170","d":"BRAKE FLUID RESERVOIR","a":true,"c":""}]},{"r":"31","v":[{"o":"0/000.290.0243","d":"BRAKE FLUID TUBE","a":false,"c":""}]},{"r":"32","v":[{"o":"0/000.480.5012","d":"RIGHT REAR WHEEL SPACER","a":true,"c":""},{"o":"0/000.480.1521","d":"REAR WHEEL SPACER L/R","a":true,"c":""}]},{"r":"33","v":[{"o":"0/000.480.1541","d":"REAR SPACER 35mm","a":true,"c":""},{"o":"0/000.480.1540","d":"REAR WHEEL SPACER","a":false,"c":""}]},{"r":"34","v":[{"o":"0/001.090.0050","d":"FRONT BRAKE DISC 260","a":false,"c":""},{"o":"0/001.090.0051","d":"REAR BRAKE DISC 200","a":true,"c":""}]},{"r":"35","v":[{"o":"0/001.090.0500","d":"FRONT BRAKE DISC 260","a":false,"c":""},{"o":"0/001.090.0020","d":"REAR BEARING SPACER","a":true,"c":""}]},{"r":"36","v":[{"o":"0/000.480.0200","d":"SPROCKET CARRIER SPACER","a":false,"c":""}]},{"r":"37","v":[{"o":"0/000.480.0850","d":"SPROCKET CARRIER","a":false,"c":""}]},{"r":"38","v":[{"o":"0/000.500.5001","d":"REAR SPROCKET Z52","a":true,"c":""},{"o":"0/000.500.0531","d":"REAR SPROCKET Z52 (BAST.>89752)","a":false,"c":""},{"o":"0/000.500.0530","d":"REAR SPROCKET Z48 (BAST.<73599)","a":false,"c":""},{"o":"0/000.500.0540","d":"ALUMINIUM REAR SPROCKET Z48","a":false,"c":""}]},{"r":"39","v":[{"o":"0/000.510.0560","d":"REAR TYRE","a":true,"c":""}]},{"r":"40","v":[{"o":"0/000.230.0530","d":"REAR SPOKES","a":false,"c":""}]},{"r":"41","v":[{"o":"0/000.540.0560","d":"REAR RIM","a":true,"c":""}]},{"r":"42","v":[{"o":"0/000.080.0092","d":"REAR HUB","a":true,"c":""},{"o":"0/000.080.0093","d":"REAR HUB","a":false,"c":""}]},{"r":"43","v":[{"o":"0/000.520.0550","d":"REAR INNER TUBE","a":true,"c":""}]},{"r":"44","v":[{"o":"0/000.220.0090","d":"RIM TAPE","a":false,"c":""}]},{"r":"45","v":[{"o":"0/000.440.1040B","d":"DISC WASHER (bag of 5)","a":true,"c":""}]},{"r":"46","v":[{"o":"0/000.460.0600B","d":"SCREW M6x16","a":true,"c":""}]},{"r":"47","v":[{"o":"0/000.440.0130B","d":"WASHER 6.5x17 (bag of 5)","a":true,"c":""}]},{"r":"48","v":[{"o":"0/000.460.0471","d":"SCREW M8x45","a":false,"c":""}]},{"r":"49","v":[{"o":"0/000.460.0651","d":"SCREW M6x45","a":true,"c":""}]},{"r":"50","v":[{"o":"0/000.440.0210","d":"SPRING WASHER","a":true,"c":""}]},{"r":"51","v":[{"o":"0/000.460.0671","d":"SCREW M8x30","a":true,"c":""}]},{"r":"52","v":[{"o":"0/000.340.0180B","d":"NUT M8 (bag of 5)","a":true,"c":""}]},{"r":"53","v":[{"o":"0/000.460.0840B","d":"DISC SCREW M8x35 (bag of 5)","a":true,"c":""}]},{"r":"54","v":[{"o":"0/000.460.0842B","d":"DISC SCREW M8x45 (bag of 5)","a":true,"c":""},{"o":"0/000.460.0673","d":"SCREW M8x40","a":true,"c":""}]},{"r":"55","v":[{"o":"0/000.460.0480","d":"SCREW M8x50","a":true,"c":""}]},{"r":"56","v":[{"o":"0/000.460.0821B","d":"COUNTERSUNK SCREW M6x20 (bag of 5)","a":true,"c":""}]},{"r":"57","v":[{"o":"0/000.340.0170B","d":"NUT M6 (bag of 5)","a":true,"c":""}]},{"r":"58","v":[{"o":"0/001.050.0210","d":"CHAIN (BAST.>73600)","a":false,"c":""},{"o":"0/001.050.0530","d":"COMPLETE CHAIN","a":false,"c":""},{"o":"0/001.050.0520","d":"COMPLETE CHAIN","a":false,"c":""}]},{"r":"59","v":[{"o":"0/000.190.0700","d":"REAR AXLE","a":false,"c":""}]},{"r":"60","v":[{"o":"0/000.860.0060","d":"REAR BRAKE CALIPER","a":false,"c":""},{"o":"0/000.860.0013","d":"REAR BRAKE CALIPER","a":false,"c":""}]}],"pins":{"30":[[19.28,15.13]],"12":[[42.47,27.71],[41.53,91.62]],"27":[[16.94,32.01]],"14":[[45.04,39.0],[39.19,90.57]],"16":[[93.6,59.02],[56.05,94.76]],"19":[[48.87,59.6]],"41":[[59.33,59.95]],"10":[[38.72,67.29]],"45":[[49.8,70.78]],"18":[[42.86,74.16]],"13":[[39.66,80.09]],"61":[[55.43,77.76]],"15":[[49.8,94.76]],"1":[[5.62,74.97]],"2":[[15.14,69.85]],"3":[[14.83,80.56]],"4":[[18.81,80.56]],"5":[[18.58,56.58]],"6":[[30.68,57.28]],"7":[[35.68,61.23]],"8":[[30.6,99.3]],"9":[[34.58,95.93]],"11":[[38.17,75.44]],"17":[[59.56,82.07]],"20":[[49.73,68.45]],"21":[[6.25,45.75]],"22":[[12.8,47.61]],"23":[[8.9,49.01]],"24":[[7.73,40.86]],"25":[[13.19,39.58]],"26":[[8.9,31.66]],"28":[[22.17,31.43]],"29":[[11.16,20.61]],"31":[[17.17,24.45]],"32":[[29.04,32.13]],"33":[[31.38,33.88]],"34":[[36.07,39.35]],"35":[[44.5,97.79]],"36":[[46.21,29.92]],"37":[[72.6,36.9]],"38":[[79.63,35.16]],"39":[[58.16,15.25]],"40":[[64.01,16.88]],"42":[[64.25,56.81]],"43":[[68.07,23.98]],"44":[[69.4,30.03]],"46":[[9.52,15.37]],"47":[[12.65,12.57]],"48":[[31.3,17.69]],"49":[[35.75,24.68]],"50":[[34.04,22.12]],"51":[[49.96,79.05]],"52":[[40.44,38.53]],"53":[[33.88,35.51]],"54":[[81.73,56.0]],"55":[[51.21,76.6]],"56":[[53.08,87.31]],"57":[[42.47,81.26]],"58":[[76.35,62.98]],"59":[[84.23,45.05]],"60":[[43.64,21.42]]}}];
|
||
const IMAGES = {"chassis":"img/chassis.webp","suspension":"img/suspension.webp","wheels":"img/wheels.webp","plastics":"img/plastics.webp","electrics":"img/electrics.webp","exhaust-radiator-tank-airfilter":"img/exhaust-radiator-tank-airfilter.webp","motord":"img/motord.webp"};
|
||
const NOTES = {
|
||
chassis:"Factory catalogue sheet 1 (titled Spike 2000; the same sheet covers 2002–2005 Spikes).",
|
||
suspension:"Heads-up: around the rear shock the drawing and the parts list number things differently. The drawing calls the shock 51 and the linkage 44–50; the parts list puts the shock under 44. Read the description before ordering.",
|
||
wheels:"Some tyre and tube lines in this list are the enduro RR sizes. Your papers say 100/80-17 front and 130/70-17 rear.",
|
||
motord:"This is the engine sheet that matches your bike (PHBN 16 carb, fitted from frame 73600 on). Rieju lists four earlier AM6 versions separately.",
|
||
plastics:"Colour variant C in the catalogue is the 2005 blue. Parts tagged “your colour” are the ones to look for.",
|
||
};
|
||
const FRAME = 550038;
|
||
const SITE = "https://www.oemmotorparts.com/en/model/rieju/spike-50/2002/drawing/";
|
||
const STATUS = {have:"Have",miss:"Missing",ord:"Ordered",skip:"Not needed"};
|
||
|
||
// ---------- fitment helpers ----------
|
||
function fits(desc){
|
||
const m = desc.match(/(BAST\.?|FRAME|Chassis|SPIKE)\s*(<=|>=|<|>)\s*(\d+)/i);
|
||
if(!m) return null;
|
||
const n=+m[3], op=m[2];
|
||
return op==="<="?FRAME<=n: op===">="?FRAME>=n: op==="<"?FRAME<n: FRAME>n;
|
||
}
|
||
function variantTags(v){
|
||
const t=[]; const f=fits(v.d);
|
||
if(f===true) t.push(["good","Fits frame 550038"]);
|
||
if(f===false) t.push(["bad","Not for your frame"]);
|
||
if(v.c==="C") t.push(["mine","Your colour (blue 2005)"]);
|
||
else if(v.c && v.c!=="") t.push(["", "Colour option "+v.c]);
|
||
t.push(v.a?["good","Orderable new"]:["","Discontinued / unavailable"]);
|
||
return t;
|
||
}
|
||
function bestName(ref){
|
||
const vs=ref.v;
|
||
const pick = vs.find(v=>v.c==="C") || vs.find(v=>fits(v.d)===true) || vs.find(v=>fits(v.d)!==false && !/DISCONTINUED/.test(v.d)) || vs[0];
|
||
return pick.d.replace(/\s*\((BAST|FRAME|Chassis|SPIKE)[^)]*\)/i,"").toLowerCase().replace(/^\w|\s\w/g,s=>s.toUpperCase()).replace(/\bL\/h\b/g,"L/H").replace(/\bR\/h\b/g,"R/H");
|
||
}
|
||
|
||
// ---------- state ----------
|
||
const EMPTY=()=>({parts:{}, links:{}, comments:{}, drawings:{}, photos:{}, users:[], me:"", spending:[], activity:[]});
|
||
let state = EMPTY();
|
||
let current=GROUPS[0].key, selRef=null, filter="all", query="", zoom=1, editing=false, confirmBulk=false, online=false;
|
||
const pid=(g,r)=>g+"__"+r;
|
||
const LS="spike50-board-v1";
|
||
function statusOf(g,r){return (state.parts[pid(g,r)]||{}).s||"none"}
|
||
|
||
async function api(method,url,body,isRaw){
|
||
const opt={method,headers:{}};
|
||
if(body!==undefined){ if(isRaw){ opt.body=body; opt.headers["Content-Type"]=body.type; } else { opt.body=JSON.stringify(body); opt.headers["Content-Type"]="application/json"; } }
|
||
let res;
|
||
try{ res=await fetch(url,opt); }catch(_){ const er=new Error("You’re offline"); er.offline=true; throw er; }
|
||
if(res.status===401){ location.href="/login"; throw new Error("Please sign in again"); }
|
||
if(res.status===403){ location.href="/password"; throw new Error("Set a new password first"); }
|
||
const data=await res.json().catch(()=>({}));
|
||
if(!res.ok) throw new Error(data.error||("Server answered "+res.status));
|
||
return data;
|
||
}
|
||
// ---------- offline storage + change queue (IndexedDB) ----------
|
||
const idb=new Promise((ok,no)=>{ setTimeout(()=>no(new Error("storage timeout")),3000); try{ const r=indexedDB.open("spike-board",1); r.onupgradeneeded=()=>{ r.result.createObjectStore("q",{autoIncrement:true}); r.result.createObjectStore("kv"); }; r.onsuccess=()=>ok(r.result); r.onerror=()=>no(r.error); }catch(e){ no(e); } });
|
||
const idbDo=(store,mode,fn)=>idb.then(db=>new Promise((ok,no)=>{ const t=db.transaction(store,mode); const out=fn(t.objectStore(store)); t.oncomplete=()=>ok(out&&"result" in out?out.result:undefined); t.onerror=()=>no(t.error); }));
|
||
const kvGet=k=>idbDo("kv","readonly",s=>s.get(k));
|
||
const kvSet=(k,v)=>idbDo("kv","readwrite",s=>s.put(v,k));
|
||
const qAdd=item=>idbDo("q","readwrite",s=>s.add(item));
|
||
const qDel=k=>idbDo("q","readwrite",s=>s.delete(k));
|
||
const qAll=()=>idb.then(db=>new Promise((ok,no)=>{ const t=db.transaction("q"); const s=t.objectStore("q"); const ks=s.getAllKeys(), vs=s.getAll(); t.oncomplete=()=>ok(ks.result.map((k,i)=>Object.assign({k},vs.result[i]))); t.onerror=()=>no(t.error); }));
|
||
let cacheTimer=null;
|
||
function saveCache(){ clearTimeout(cacheTimer); cacheTimer=setTimeout(()=>{ kvSet("state",JSON.parse(JSON.stringify(state))).catch(()=>{}); },150); }
|
||
let pendingN=0, tmpId=-1;
|
||
const stamp=()=>new Date().toLocaleTimeString([], {hour:"2-digit",minute:"2-digit"});
|
||
function badgeNow(){ setBadge(online ? (pendingN?`Syncing ${pendingN} saved change${pendingN>1?"s":""}…`:"Saved on your server · "+stamp()) : (pendingN?`Offline · ${pendingN} change${pendingN>1?"s":""} saved on this phone, will sync later`:"Offline · showing the copy saved on this device")); }
|
||
// send a change; if there's no connection keep it on the device and apply it locally
|
||
async function send(method,url,body,apply){
|
||
try{ return await api(method,url,body); }
|
||
catch(e){
|
||
if(!e.offline) throw e;
|
||
try{ await qAdd({method,url,body}); }catch(_){ throw e; }
|
||
online=false; pendingN++; if(apply) apply(); saveCache(); badgeNow(); render();
|
||
return {queued:true};
|
||
}
|
||
}
|
||
let flushing=false;
|
||
async function flushQueue(){
|
||
if(flushing) return pendingN; flushing=true;
|
||
try{
|
||
for(const it of await qAll()){
|
||
try{ await api(it.method,it.url,it.body); await qDel(it.k); }
|
||
catch(e){ if(e.offline) break; await qDel(it.k); toast("A change saved offline was rejected: "+e.message); }
|
||
}
|
||
pendingN=(await qAll()).length;
|
||
}catch(_){ pendingN=0; }
|
||
finally{ flushing=false; }
|
||
return pendingN;
|
||
}
|
||
function setBadge(t){ $("#savebadge").textContent=t; }
|
||
async function reload(){
|
||
await flushQueue();
|
||
if(pendingN){ online=false; badgeNow(); render(); return; }
|
||
try{ const s=await api("GET","api/state"); state=Object.assign(EMPTY(),s); online=true; saveCache(); badgeNow(); }
|
||
catch(e){ online=false; badgeNow(); }
|
||
render();
|
||
}
|
||
const writing={};
|
||
async function savePart(g,r,patch){
|
||
const id=pid(g,r); const cur=Object.assign({}, state.parts[id]||{}, patch);
|
||
state.parts[id]=cur; if(patch.s!==undefined) render();
|
||
saveCache();
|
||
try{ const r=await (writing[id]=(writing[id]||Promise.resolve()).catch(()=>{}).then(()=>send("PUT","api/parts/"+id,{s:cur.s||"none",note:cur.note||""}))); if(!r.queued){ online=true; badgeNow(); } }
|
||
catch(e){ toast("Couldn’t save: "+e.message); setBadge("Last change not saved. Check the server."); }
|
||
}
|
||
let pinTimer=null;
|
||
function savePins(g){
|
||
clearTimeout(pinTimer);
|
||
pinTimer=setTimeout(async()=>{
|
||
try{ saveCache(); await send("PUT","api/drawings/"+g+"/pins",{pins:(state.drawings[g]||{}).pins||null}); }catch(e){ toast("Couldn’t save marker positions: "+e.message); }
|
||
},400);
|
||
}
|
||
function pinsFor(g){ const d=state.drawings[g]; if(d&&d.pins) return d.pins; const G=GROUPS.find(x=>x.key===g); return G.pins||{}; }
|
||
function imageFor(g){ const d=state.drawings[g]; if(d&&d.image) return d.image; return IMAGES[g]||null; }
|
||
const linksOf=(g,r)=>state.links[pid(g,r)]||[];
|
||
const commentsOf=(g,r)=>state.comments[pid(g,r)]||[];
|
||
function when(ms){ return new Date(ms).toLocaleString([], {day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit"}); }
|
||
function host(u){ try{ return new URL(u).hostname.replace(/^www\./,""); }catch(_){ return u; } }
|
||
|
||
// ---------- render ----------
|
||
const $=s=>document.querySelector(s);
|
||
function counts(keys){
|
||
const c={have:0,miss:0,ord:0,skip:0,none:0,total:0};
|
||
for(const G of GROUPS){ if(keys && !keys.includes(G.key)) continue; for(const r of G.refs){ c[statusOf(G.key,r.r)]++; c.total++; } }
|
||
return c;
|
||
}
|
||
function renderHead(){
|
||
const c=counts();
|
||
$("#who").innerHTML=`<span>Online:</span>${(state.users||[]).map(u=>`<span class="chip" title="${u.online?"online now":(u.last?"last seen "+when(u.last):"never signed in")}"><span class="dot" style="background:${u.online?"var(--have)":"var(--none)"}"></span>${esc(u.name)}${u.name===state.me?" (you)":""}</span>`).join("")}<span>· <a href="/password">Password</a> · <a href="/logout">Log out</a></span>`;
|
||
$("#tally").innerHTML = ["have","miss","ord","skip","none"].map(s=>`<span class="chip s-${s}"><span class="dot"></span>${s==="none"?"Unchecked":STATUS[s]} <b class="mono">${c[s]}</b></span>`).join("");
|
||
$("#bar").innerHTML = ["have","skip","ord","miss"].map(s=>`<i style="width:${c[s]/c.total*100}%;background:var(--${s})"></i>`).join("");
|
||
}
|
||
function renderTabs(){
|
||
const shopN=counts().miss+counts().ord;
|
||
$("#tabs").innerHTML = GROUPS.map(G=>{
|
||
const c=counts([G.key]); const done=c.have+c.skip;
|
||
return `<button class="tab" role="tab" aria-selected="${current===G.key}" data-g="${G.key}">
|
||
<span class="t">${G.title}</span>
|
||
<span class="bar">${["have","skip","ord","miss"].map(s=>`<i style="width:${c[s]/c.total*100}%;background:var(--${s})"></i>`).join("")}</span>
|
||
<span class="m"><span>${done}/${c.total} done</span><span>${c.miss?`<span style="color:var(--miss)">${c.miss} missing</span>`:(imageFor(G.key)?"drawing":"no drawing yet")}</span></span></button>`;
|
||
}).join("") + `<button class="tab photos" role="tab" aria-selected="${current==="gallery"}" data-g="gallery"><span class="t">Photos</span><span class="m"><span>${Object.values(state.photos||{}).reduce((n,l)=>n+l.length,0)} photos</span><span></span></span></button>`
|
||
+ `<button class="tab" role="tab" aria-selected="${current==="spending"}" data-g="spending"><span class="t">Spending</span><span class="m"><span>${eur((state.spending||[]).reduce((n,x)=>n+x.cents,0))}</span><span></span></span></button>`
|
||
+ `<button class="tab" role="tab" aria-selected="${current==="activity"}" data-g="activity"><span class="t">Activity</span><span class="m"><span>who did what</span><span></span></span></button>`
|
||
+ `<button class="tab shop" role="tab" aria-selected="${current==="shop"}" data-g="shop"><span class="t">Shopping list</span><span class="m"><span>${shopN} parts to get</span><span></span></span></button>`;
|
||
}
|
||
function visibleRefs(G){
|
||
return G.refs.filter(r=>{
|
||
const s=statusOf(G.key,r.r);
|
||
if(filter!=="all" && s!==filter) return false;
|
||
if(query){ const q=query.toLowerCase(); if(!(r.r===q || r.v.some(v=>v.o.toLowerCase().includes(q)||v.d.toLowerCase().includes(q)))) return false; }
|
||
return true;
|
||
});
|
||
}
|
||
function renderMain(){
|
||
if(current==="shop") return renderShop();
|
||
if(current==="gallery") return renderGallery();
|
||
if(current==="spending") return renderSpending();
|
||
if(current==="activity") return renderActivity();
|
||
const G=GROUPS.find(x=>x.key===current);
|
||
const img=imageFor(G.key); const pins=pinsFor(G.key);
|
||
const refset=new Set(G.refs.map(r=>r.r));
|
||
let drawing;
|
||
if(img){
|
||
const pinHtml=Object.entries(pins).flatMap(([r,list])=>list.map((p,i)=>{
|
||
const orphan=!refset.has(r); const s=orphan?"none":statusOf(G.key,r);
|
||
return `<button class="pin s-${s}${selRef===r?" sel":""}${orphan?" orphan":""}" style="left:${p[0]}%;top:${p[1]}%" data-r="${r}" data-i="${i}" aria-label="Part ${r}${orphan?" (not in parts list)":", "+(STATUS[s]||"unchecked")}"></button>`;
|
||
})).join("");
|
||
drawing = `<div class="viewport" id="vp"><div class="stage${editing?" editing":""}" id="stage" style="width:${zoom*100}%"><img src="${img}" alt="Exploded drawing: ${G.title}" draggable="false">${pinHtml}</div></div>`;
|
||
} else {
|
||
drawing = `<div class="nodraw"><h3>No drawing for this group yet</h3>
|
||
<p>Open the factory drawing for this group, save it as an image, then add it here. You can then click each number on the drawing to place a marker.</p>
|
||
<div class="tools"><a class="btn" href="${SITE+G.key}" target="_blank" rel="noopener">Open drawing on OEM Motorparts</a>
|
||
${online?`<label class="btn primary" for="upl">Upload drawing</label><input type="file" id="upl" accept="image/png,image/jpeg,image/webp,image/gif" hidden>`:""}</div></div>`;
|
||
}
|
||
const c=counts([G.key]);
|
||
const rows=visibleRefs(G).map(r=>{
|
||
const s=statusOf(G.key,r.r); const pr=state.parts[pid(G.key,r.r)]||{}; const note=pr.note;
|
||
const onDraw = pins[r.r]?"":" · not on drawing";
|
||
const nl=linksOf(G.key,r.r).length, nc=commentsOf(G.key,r.r).length;
|
||
const extra=(nl?` · ${nl} link${nl>1?"s":""}`:"")+(nc?` · ${nc} comment${nc>1?"s":""}`:"");
|
||
let html=`<button class="row s-${s}${selRef===r.r?" sel":""}" data-r="${r.r}"><span class="ref">${r.r}</span>
|
||
<span style="min-width:0"><span class="rname">${esc(bestName(r))}</span><br><span class="rsub mono">${r.v.length>1?r.v.length+" variants":esc(r.v[0].o)}${img?onDraw:""}${extra}${pr.by&&s!=="none"?" · "+esc(pr.by):""}${note?" · "+esc(note):""}</span></span>
|
||
<span class="stat"><span class="dot s-${s}" style="display:inline-block;vertical-align:middle"></span> ${s==="none"?"":STATUS[s]}</span></button>`;
|
||
if(selRef===r.r) html+=detailHtml(G,r);
|
||
return html;
|
||
}).join("") || `<div class="empty">No parts match this filter.</div>`;
|
||
const orphanSel = selRef && !refset.has(selRef) ? `<div class="detail"><b>Number ${esc(selRef)} on the drawing</b><span class="rsub">This number isn’t in the 2002–2005 parts list. It may be a kit number or a part that only existed on the 2000 model. Check it on the website before ordering.</span>${editing?`<button class="btn" data-act="delpin">Remove this marker</button>`:""}</div>`:"";
|
||
$("#main").innerHTML = `<div class="main">
|
||
<section class="card">
|
||
<div class="dhead"><h2>${G.title}</h2>
|
||
<div class="tools">${img?`<button class="btn primary" data-act="fs">⤢ Full screen</button><button class="btn" data-z="-1" aria-label="Zoom out">−</button><span class="mono" style="min-width:3.5em;text-align:center">${Math.round(zoom*100)}%</span><button class="btn" data-z="1" aria-label="Zoom in">+</button>
|
||
<button class="btn${editing?" on":""}" data-act="edit">${editing?"Done moving markers":"Move markers"}</button>
|
||
${online?`<label class="btn" for="upl">Replace drawing</label><input type="file" id="upl" accept="image/png,image/jpeg,image/webp,image/gif" hidden>`:""}`:""}
|
||
<a class="btn" href="${SITE+G.key}" target="_blank" rel="noopener">Source</a></div></div>
|
||
${drawing}
|
||
${editing?`<div class="note"><b>Marker mode:</b> drag a marker to move it. Select a part in the list, then click the drawing to add a marker for it.</div>`:""}
|
||
${NOTES[G.key]?`<div class="note">${NOTES[G.key]}</div>`:""}
|
||
</section>
|
||
<section class="card">
|
||
<div class="lhead">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap"><span class="lbl">${c.total} numbered parts</span>
|
||
${c.none? (confirmBulk?`<span class="confirm">Mark ${c.none} unchecked parts as Have? <button class="btn primary" data-act="bulkyes">Yes</button><button class="btn" data-act="bulkno">Cancel</button></span>`:`<button class="btn" data-act="bulk">Mark the rest as Have</button>`):""}</div>
|
||
<input type="search" id="q" placeholder="Search number, part name or OEM no." value="${esc(query)}">
|
||
<div class="filters">${[["all","All"],["none","Unchecked"],["miss","Missing"],["ord","Ordered"],["have","Have"],["skip","Not needed"]].map(([k,l])=>`<button class="btn${filter===k?" on":""}" data-f="${k}">${l}</button>`).join("")}</div>
|
||
</div>
|
||
${orphanSel}
|
||
<div class="list" id="list">${rows}</div>
|
||
</section></div>`;
|
||
if(selRef){ const el=document.querySelector(`.row.sel`); if(el && el.scrollIntoViewIfNeeded) el.scrollIntoViewIfNeeded(); }
|
||
}
|
||
function detailHtml(G,r){
|
||
const p=state.parts[pid(G.key,r.r)]||{}; const s=p.s||"none";
|
||
return `<div class="detail" data-detail="${r.r}">
|
||
<div class="sbtns">${Object.entries(STATUS).map(([k,l])=>`<button data-s="${k}" aria-pressed="${s===k}">${l}</button>`).join("")}</div>
|
||
<div class="vars"><span class="lbl">Catalogue entries for no. ${r.r}</span>${r.v.map(v=>{const f=fits(v.d); return `<div class="var${f===false||(v.c&&v.c!=="C"&&G.key==="plastics")?" no":""}">
|
||
<a class="o" href="https://www.oemmotorparts.com/en/search?filter=${encodeURIComponent(v.o)}" target="_blank" rel="noopener">${esc(v.o)}</a><a class="o" style="font-size:12px;opacity:.8" title="Search Google for this number" href="https://www.google.com/search?q=${encodeURIComponent('"'+v.o+'" Rieju Spike 50')}" target="_blank" rel="noopener">Google</a><span class="d">${esc(v.d)}</span>
|
||
<span class="tags">${variantTags(v).map(([c,t])=>`<span class="tag ${c}">${t}</span>`).join("")}</span></div>`}).join("")}</div>
|
||
<label class="lbl" for="note">Note</label>
|
||
<textarea id="note" rows="2" placeholder="Where it is, condition, where you ordered it…">${esc(p.note||"")}</textarea>
|
||
<div class="sub"><span class="lbl">Links</span>
|
||
${linksOf(G.key,r.r).map(l=>`<div class="item"><span><a href="${esc(l.url)}" target="_blank" rel="noopener noreferrer">${esc(l.label||host(l.url))}</a><br><span class="when">${esc(host(l.url))} · added ${l.by?"by "+esc(l.by)+" ":""}${when(l.at)}</span></span>${l.id>0?`<button class="x" data-dellink="${l.id}" aria-label="Remove link">×</button>`:`<span class="when">waiting</span>`}</div>`).join("")}
|
||
<form class="addrow" data-form="link"><input type="url" id="lurl" placeholder="https://… (eBay listing, shop, forum post)" required><input type="text" id="llabel" placeholder="Label (optional)"><button class="btn primary" type="submit">Add link</button></form>
|
||
</div>
|
||
<div class="sub"><span class="lbl">Comments</span>
|
||
${commentsOf(G.key,r.r).map(c=>`<div class="item"><span><p>${esc(c.text)}</p><span class="when">${c.by?esc(c.by)+" · ":""}${when(c.at)}</span></span>${c.id>0?`<button class="x" data-delcomment="${c.id}" aria-label="Delete comment">×</button>`:`<span class="when">waiting</span>`}</div>`).join("")}
|
||
<form class="sub" data-form="comment"><textarea id="ctext" rows="2" placeholder="Write a comment, e.g. “Ordered from Motostion, 12 € + shipping”" required></textarea><div><button class="btn primary" type="submit">Add comment</button></div></form>
|
||
</div>
|
||
<div class="sub"><span class="lbl">Photos</span>
|
||
<div class="pgrid">${photoItems(pid(G.key,r.r)).map((p,i)=>`<button class="ph" data-ph="${pid(G.key,r.r)}|${i}" aria-label="Open photo"><img loading="lazy" src="${p.thumb}" alt="">${p.pending?`<i class="pend">waiting</i>`:""}</button>`).join("")}</div>
|
||
<div><label class="btn" for="pupl">+ Add photo</label><input type="file" id="pupl" accept="image/*" multiple hidden></div>
|
||
</div>
|
||
<div class="sub"><span class="lbl">History</span>${state.activity.filter(a=>a.part===pid(G.key,r.r)).slice(0,8).map(a=>`<div class="rsub"><b>${esc(a.by)}</b> ${esc(actText(a))} · ${when(a.at)}</div>`).join("")||`<div class="rsub">No changes yet.</div>`}</div>
|
||
${editing&&pinsFor(G.key)[r.r]?`<button class="btn" data-act="delpin">Remove this part’s markers</button>`:""}
|
||
</div>`;
|
||
}
|
||
function renderShop(){
|
||
const sections=GROUPS.map(G=>{
|
||
const items=G.refs.filter(r=>["miss","ord"].includes(statusOf(G.key,r.r)));
|
||
if(!items.length) return "";
|
||
return `<div><h3 style="font-size:16px;margin-bottom:6px">${G.title}</h3><div class="tblwrap"><table><thead><tr><th>No.</th><th>Part</th><th>OEM number(s)</th><th>Status</th><th>Note & links</th></tr></thead><tbody>${items.map(r=>{
|
||
const p=state.parts[pid(G.key,r.r)]||{};
|
||
const good=r.v.filter(v=>fits(v.d)!==false && (G.key!=="plastics"||!v.c||v.c==="C"));
|
||
return `<tr><td class="mono">${r.r}</td><td>${esc(bestName(r))}</td><td class="mono">${(good.length?good:r.v).map(v=>`<a href="https://www.oemmotorparts.com/en/search?filter=${encodeURIComponent(v.o)}" target="_blank" rel="noopener">${esc(v.o)}</a>${v.a?"":" <span class=\"tag\">unavailable</span>"}`).join("<br>")}</td><td><span class="chip s-${p.s}"><span class="dot"></span>${STATUS[p.s]}</span></td><td>${esc(p.note||"")}${linksOf(G.key,r.r).map(l=>`<br><a href="${esc(l.url)}" target="_blank" rel="noopener noreferrer">${esc(l.label||host(l.url))}</a>`).join("")}</td></tr>`}).join("")}</tbody></table></div></div>`;
|
||
}).join("");
|
||
$("#main").innerHTML=`<section class="card shop"><div style="display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center"><h2 style="font-size:20px">Shopping list</h2><button class="btn primary" data-act="copy">Copy list</button></div>
|
||
<p class="rsub" style="margin:0">Everything marked Missing or Ordered. Variants that don’t fit frame 550038 or aren’t your colour are left out. Unavailable numbers are worth searching on eBay and at breakers.</p>
|
||
${sections||`<div class="empty">Nothing marked Missing yet. Open a drawing group and mark the parts you don’t have.</div>`}</section>`;
|
||
}
|
||
function render(){ renderHead(); renderTabs(); renderMain(); }
|
||
function esc(s){return String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">","\"":"""}[c]))}
|
||
function toast(t){const el=$("#toast"); el.textContent=t; el.hidden=false; clearTimeout(toast.t); toast.t=setTimeout(()=>el.hidden=true,2600);}
|
||
|
||
// ---------- events ----------
|
||
document.addEventListener("click",async e=>{
|
||
if(e.target.closest("#stage") && !editing && !e.target.closest(".pin")){ openDrawing(current); return; }
|
||
const t=e.target.closest("button,[data-g]"); if(!t) return;
|
||
if(t.dataset.go){ const [g,r]=t.dataset.go.split("|"); current=g; selRef=r; filter="all"; render(); try{localStorage.setItem(LS+"-tab",current)}catch(_){} return; }
|
||
if(t.dataset.delspend){ const id=+t.dataset.delspend; if(!confirm("Remove this spending entry?")) return;
|
||
try{ const r=await send("DELETE","api/spending/"+id,undefined,()=>{ state.spending=state.spending.filter(x=>x.id!==id); }); if(!r.queued) await reload(); }catch(err){ toast("Couldn’t remove: "+err.message); } return; }
|
||
if(t.dataset.ph){ const [sc,i]=t.dataset.ph.split("|"); openPhotos(photoItems(sc),+i); return; }
|
||
if(t.dataset.g){ current=t.dataset.g; selRef=null; editing=false; confirmBulk=false; zoom=1; render(); try{localStorage.setItem(LS+"-tab",current)}catch(_){} return; }
|
||
if(t.classList.contains("pin")){ if(dragMoved) return; selRef=t.dataset.r; filter="all"; render(); return; }
|
||
if(t.classList.contains("row")){ selRef = selRef===t.dataset.r?null:t.dataset.r; render(); return; }
|
||
if(t.dataset.s && t.closest(".detail")){ const cur=statusOf(current,selRef); savePart(current,selRef,{s: cur===t.dataset.s?"none":t.dataset.s}); return; }
|
||
if(t.dataset.f){ filter=t.dataset.f; render(); return; }
|
||
if(t.dataset.z){ zoom=Math.min(3,Math.max(1,zoom+ (+t.dataset.z)*0.5)); render(); return; }
|
||
if(t.dataset.dellink){ const id=+t.dataset.dellink; try{ const r=await send("DELETE","api/links/"+id,undefined,()=>{ for(const k in state.links) state.links[k]=state.links[k].filter(l=>l.id!==id); }); if(!r.queued) await reload(); }catch(err){ toast("Couldn’t remove: "+err.message); } return; }
|
||
if(t.dataset.delcomment){ const id=+t.dataset.delcomment; try{ const r=await send("DELETE","api/comments/"+id,undefined,()=>{ for(const k in state.comments) state.comments[k]=state.comments[k].filter(c=>c.id!==id); }); if(!r.queued) await reload(); }catch(err){ toast("Couldn’t delete: "+err.message); } return; }
|
||
const a=t.dataset.act;
|
||
if(a==="fs"){ openDrawing(current); return; }
|
||
if(a==="edit"){ editing=!editing; render(); }
|
||
if(a==="bulk"){ confirmBulk=true; render(); }
|
||
if(a==="bulkno"){ confirmBulk=false; render(); }
|
||
if(a==="bulkyes"){ confirmBulk=false; const G=GROUPS.find(x=>x.key===current); for(const r of G.refs){ if(statusOf(G.key,r.r)==="none") await savePart(G.key,r.r,{s:"have"}); } toast("Marked as Have"); }
|
||
if(a==="delpin"){ const p=Object.assign({},pinsFor(current)); delete p[selRef]; state.drawings[current]=Object.assign({},state.drawings[current],{pins:p}); savePins(current); render(); }
|
||
if(a==="copy"){ const txt=GROUPS.flatMap(G=>G.refs.filter(r=>["miss","ord"].includes(statusOf(G.key,r.r))).map(r=>{const good=r.v.filter(v=>fits(v.d)!==false&&(G.key!=="plastics"||!v.c||v.c==="C"));return `${G.title} #${r.r}\t${bestName(r)}\t${(good.length?good:r.v).map(v=>v.o).join(" / ")}\t${STATUS[statusOf(G.key,r.r)]}`;})).join("\n");
|
||
try{ await navigator.clipboard.writeText(txt); toast("Copied"); }catch(_){ toast("Copy didn’t work here. Select the table and copy it instead."); } }
|
||
});
|
||
document.addEventListener("input",e=>{
|
||
if(e.target.id==="q"){ query=e.target.value.trim(); const pos=e.target.selectionStart; renderMain(); const q=$("#q"); q.focus(); q.setSelectionRange(pos,pos); }
|
||
});
|
||
document.addEventListener("change",async e=>{
|
||
if(e.target.id==="note"){ savePart(current,selRef,{note:e.target.value}); }
|
||
if((e.target.id==="pupl"||e.target.id==="gupl") && e.target.files.length){
|
||
const files=[...e.target.files], scope=e.target.id==="gupl"?"gallery":pid(current,selRef), cap=e.target.id==="gupl"?($("#gcap")||{}).value:"";
|
||
e.target.value=""; await addPhotos(files,scope,cap);
|
||
}
|
||
if(e.target.id==="upl" && e.target.files[0]){
|
||
toast("Uploading…");
|
||
try{ await api("POST","api/drawings/"+current+"/image",e.target.files[0],true); await reload(); editing=true; render(); toast("Drawing added. Select a part, then click its number on the drawing."); }
|
||
catch(err){ toast("Upload failed: "+err.message); }
|
||
}
|
||
});
|
||
document.addEventListener("submit",async e=>{
|
||
const f=e.target.closest("form[data-form]"); if(!f) return; e.preventDefault(); if(f.dataset.form==="spend") return;
|
||
const id=pid(current,selRef);
|
||
try{
|
||
let r;
|
||
if(f.dataset.form==="link"){ const b={url:$("#lurl").value,label:$("#llabel").value};
|
||
r=await send("POST","api/parts/"+id+"/links",b,()=>{ (state.links[id]=state.links[id]||[]).push({id:tmpId--,url:b.url,label:b.label,at:Date.now()}); }); toast(r.queued?"Link saved offline":"Link added"); }
|
||
else { const b={text:$("#ctext").value};
|
||
r=await send("POST","api/parts/"+id+"/comments",b,()=>{ (state.comments[id]=state.comments[id]||[]).push({id:tmpId--,text:b.text,at:Date.now()}); }); toast(r.queued?"Comment saved offline":"Comment added"); }
|
||
if(!r.queued) await reload();
|
||
}catch(err){ toast(err.message); }
|
||
});
|
||
// marker drag + place
|
||
let drag=null, dragMoved=false;
|
||
document.addEventListener("pointerdown",e=>{
|
||
if(!editing) return; const pin=e.target.closest(".pin"); const stage=e.target.closest("#stage"); if(!stage) return;
|
||
const rect=stage.getBoundingClientRect();
|
||
if(pin){ drag={pin,r:pin.dataset.r,i:+pin.dataset.i,rect}; dragMoved=false; pin.setPointerCapture(e.pointerId); e.preventDefault(); return; }
|
||
if(selRef){ const x=(e.clientX-rect.left)/rect.width*100, y=(e.clientY-rect.top)/rect.height*100;
|
||
const p=JSON.parse(JSON.stringify(pinsFor(current))); (p[selRef]=p[selRef]||[]).push([+x.toFixed(2),+y.toFixed(2)]);
|
||
state.drawings[current]=Object.assign({},state.drawings[current],{pins:p}); savePins(current); render(); }
|
||
else toast("Select a part in the list first.");
|
||
});
|
||
document.addEventListener("pointermove",e=>{ if(!drag) return; dragMoved=true;
|
||
const x=Math.min(100,Math.max(0,(e.clientX-drag.rect.left)/drag.rect.width*100)), y=Math.min(100,Math.max(0,(e.clientY-drag.rect.top)/drag.rect.height*100));
|
||
drag.pin.style.left=x+"%"; drag.pin.style.top=y+"%"; drag.x=x; drag.y=y; });
|
||
document.addEventListener("pointerup",()=>{ if(!drag) return;
|
||
if(dragMoved && drag.x!=null){ const p=JSON.parse(JSON.stringify(pinsFor(current))); if(p[drag.r]&&p[drag.r][drag.i]) p[drag.r][drag.i]=[+drag.x.toFixed(2),+drag.y.toFixed(2)];
|
||
state.drawings[current]=Object.assign({},state.drawings[current],{pins:p}); savePins(current); }
|
||
else { selRef=drag.r; render(); }
|
||
drag=null; setTimeout(()=>dragMoved=false,0); });
|
||
|
||
// ---------- spending, activity ----------
|
||
const eur=c=>(c/100).toLocaleString("de-DE",{style:"currency",currency:"EUR"});
|
||
function groupTitle(k){ const G=GROUPS.find(x=>x.key===k); return G?G.title:k; }
|
||
function actText(a){
|
||
const d=a.detail||"";
|
||
switch(a.action){
|
||
case "status": return "set the status to "+(STATUS[d]||"unchecked");
|
||
case "note": return "edited the note";
|
||
case "link_add": return `added the link “${d}”`;
|
||
case "link_del": return `removed the link “${d}”`;
|
||
case "comment_add": return `commented “${d}”`;
|
||
case "comment_del": return "deleted a comment";
|
||
case "photo_add": return "added a photo"+(d?` “${d}”`:"");
|
||
case "photo_del": return "deleted a photo";
|
||
case "markers": return "moved markers on "+groupTitle(d);
|
||
case "spend_add": { const [m,...w]=d.split(" · "); return `spent €${m} on ${w.join(" · ")}`; }
|
||
case "spend_del": { const [m,...w]=d.split(" · "); return `removed the spending €${m} ${w.join(" · ")}`; }
|
||
default: return a.action;
|
||
}
|
||
}
|
||
function settlements(bal){
|
||
const give=bal.filter(b=>b.v<-0.5).map(b=>({n:b.n,v:-b.v})).sort((a,b)=>b.v-a.v), get=bal.filter(b=>b.v>0.5).map(b=>({n:b.n,v:b.v})).sort((a,b)=>b.v-a.v), out=[];
|
||
let i=0,j=0; while(i<give.length&&j<get.length){ const m=Math.min(give[i].v,get[j].v); out.push({from:give[i].n,to:get[j].n,c:Math.round(m)}); give[i].v-=m; get[j].v-=m; if(give[i].v<0.5) i++; if(get[j].v<0.5) j++; }
|
||
return out;
|
||
}
|
||
function renderSpending(){
|
||
const sp=state.spending||[]; const names=(state.users||[]).map(u=>u.name); const n=Math.max(1,names.length);
|
||
const total=sp.reduce((a,x)=>a+x.cents,0), share=total/n;
|
||
const paid=Object.fromEntries(names.map(u=>[u,0])); for(const x of sp) paid[x.by]=(paid[x.by]||0)+x.cents;
|
||
const bal=names.map(u=>({n:u,v:(paid[u]||0)-share})); const pay=settlements(bal);
|
||
$("#main").innerHTML=`<section class="card shop">
|
||
<div><h2 style="font-size:20px">Spending</h2><p class="rsub" style="margin:4px 0 0">Everyone adds what they paid for the bike. The total is split equally between ${n} people (${names.map(esc).join(", ")}).</p></div>
|
||
<form class="addrow" data-form="spend"><input type="text" inputmode="decimal" id="samt" placeholder="Amount in €" required autocomplete="off"><input type="text" id="swhat" placeholder="What was it for?" required maxlength="200" autocomplete="off"><button class="btn primary" type="submit">Add</button></form>
|
||
<div class="sumgrid"><div class="sum"><span class="rsub">Total spent</span><b>${eur(total)}</b></div><div class="sum"><span class="rsub">Fair share each</span><b>${eur(Math.round(share))}</b></div>
|
||
${bal.map(b=>`<div class="sum"><span class="rsub">${esc(b.n)} paid ${eur(paid[b.n]||0)}</span><b class="${b.v>=0.5?"pos":b.v<=-0.5?"neg":""}">${b.v>=0.5?"+":""}${eur(Math.round(b.v))}</b></div>`).join("")}</div>
|
||
<div><span class="lbl">Settle up</span>${pay.length?pay.map(x=>`<div class="item"><span><b>${esc(x.from)}</b> pays <b>${esc(x.to)}</b></span><span class="amt">${eur(x.c)}</span></div>`).join(""):`<div class="rsub">${total?"Everyone is even.":"Nothing spent yet."}</div>`}</div>
|
||
<div class="sub"><span class="lbl">All entries</span>${sp.map(x=>`<div class="item"><span><b>${esc(x.what)}</b><br><span class="when">${esc(x.by)} · ${when(x.at)}${x.id<0?" · waiting to upload":""}</span></span><span style="display:flex;gap:6px;align-items:center"><span class="amt">${eur(x.cents)}</span>${x.by===state.me&&x.id>0?`<button class="x" data-delspend="${x.id}" aria-label="Remove entry">×</button>`:""}</span></div>`).join("")||`<div class="rsub">No entries yet.</div>`}</div>
|
||
</section>`;
|
||
}
|
||
function renderActivity(){
|
||
const list=state.activity||[];
|
||
$("#main").innerHTML=`<section class="card shop"><div><h2 style="font-size:20px">Activity</h2><p class="rsub" style="margin:4px 0 0">The latest changes by everyone. Tap a part to jump to it.</p></div>
|
||
<div class="sub">${list.map(a=>{ const [g,r]=(a.part||"").split("__"); const G=GROUPS.find(x=>x.key===g);
|
||
return `<div class="item"><span><b>${esc(a.by)}</b> ${esc(actText(a))}${G?` · <button class="btn" style="padding:1px 8px;font-size:12px" data-go="${a.part.replace("__","|")}">${esc(G.title)} #${esc(r)}</button>`:""}<br><span class="when">${when(a.at)}</span></span></div>`; }).join("")||`<div class="empty">Nothing yet.</div>`}</div></section>`;
|
||
}
|
||
|
||
// ---------- photos ----------
|
||
function partLabel(k){ const [g,r]=k.split("__"); const G=GROUPS.find(x=>x.key===g); return G?`${G.title} #${r}`:k; }
|
||
function photoItems(scope){
|
||
const mk=(k,p)=>Object.assign({},p,{scope:k,label:k==="gallery"?"":partLabel(k)});
|
||
if(scope==="parts") return Object.entries(state.photos||{}).filter(([k])=>k!=="gallery").flatMap(([k,l])=>l.map(p=>mk(k,p)));
|
||
return ((state.photos||{})[scope]||[]).map(p=>mk(scope,p));
|
||
}
|
||
async function prepPhoto(file){
|
||
const bmp=await createImageBitmap(file,{imageOrientation:"from-image"});
|
||
const mk=(max,q)=>{ const k=Math.min(1,max/Math.max(bmp.width,bmp.height)); const c=document.createElement("canvas"); c.width=Math.round(bmp.width*k); c.height=Math.round(bmp.height*k); c.getContext("2d").drawImage(bmp,0,0,c.width,c.height); return c.toDataURL("image/jpeg",q); };
|
||
const out={image:mk(2000,.85),thumb:mk(400,.75)}; if(bmp.close) bmp.close(); return out;
|
||
}
|
||
async function addPhotos(files,scope,caption){
|
||
let n=0, queued=false;
|
||
toast(`Adding ${files.length} photo${files.length>1?"s":""}…`);
|
||
for(const f of files){
|
||
try{
|
||
const d=await prepPhoto(f); const body={scope,caption:caption||"",image:d.image,thumb:d.thumb};
|
||
const r=await send("POST","api/photos",body,()=>{ (state.photos[scope]=state.photos[scope]||[]).unshift({id:tmpId--,caption:body.caption,at:Date.now(),src:d.image,thumb:d.thumb,pending:true}); });
|
||
if(r.queued) queued=true; n++;
|
||
}catch(e){ toast("Couldn’t add “"+f.name+"”: "+e.message); }
|
||
}
|
||
if(!queued) await reload(); else render();
|
||
if(n) toast(queued?`${n} photo${n>1?"s":""} saved on this phone, will upload when online`:`${n} photo${n>1?"s":""} added`);
|
||
}
|
||
let gq="";
|
||
function renderGallery(){
|
||
const all=photoItems("gallery"); const list=gq?all.filter(p=>p.caption.toLowerCase().includes(gq)):all;
|
||
const parts=photoItems("parts");
|
||
const tile=(p,key,i)=>`<button class="ph" data-ph="${key}|${i}" aria-label="Open photo"><img loading="lazy" src="${p.thumb}" alt="${esc(p.caption)}">${p.pending?`<i class="pend">waiting</i>`:""}${(p.label||p.caption)?`<span>${esc(p.label?p.label+(p.caption?" · "+p.caption:""):p.caption)}</span>`:""}</button>`;
|
||
$("#main").innerHTML=`<section class="card shop">
|
||
<div><h2 style="font-size:20px">Photos & disassembly</h2><p class="rsub" style="margin:4px 0 0">Shared photos of the bike: what you took apart, condition, how things fit. Tap a photo to open and zoom. Photos of single parts are added inside the part (open a part, then “Add photo”).</p></div>
|
||
<div class="gup"><input type="text" id="gcap" placeholder="Caption for the new photos (optional), e.g. “Engine out, left side”"><label class="btn primary" for="gupl">+ Add photos</label><input type="file" id="gupl" accept="image/*" multiple hidden></div>
|
||
${all.length>6?`<input type="search" id="gq" placeholder="Search captions" value="${esc(gq)}">`:""}
|
||
${list.length?`<div class="pgrid big">${list.map((p,i)=>tile(p,"gallery",all.indexOf(p))).join("")}</div>`:`<div class="empty">${all.length?"No photos match.":"No photos yet. Add the first one."}</div>`}
|
||
${parts.length?`<div><h3 style="font-size:16px;margin-bottom:8px">Photos attached to parts</h3><div class="pgrid big">${parts.map((p,i)=>tile(p,"parts",i)).join("")}</div></div>`:""}
|
||
</section>`;
|
||
}
|
||
document.addEventListener("input",e=>{ if(e.target.id==="gq"){ gq=e.target.value.trim().toLowerCase(); const pos=e.target.selectionStart; renderGallery(); const q=$("#gq"); q.focus(); q.setSelectionRange(pos,pos); } });
|
||
|
||
// ---------- full-screen viewer (photos + drawings): pinch, drag, wheel, double-tap ----------
|
||
const vw={open:false,s:1,tx:0,ty:0,nw:0,nh:0,fw:0,fh:0,cw:0,ch:0,mode:"",list:[],i:0};
|
||
const vEl={};
|
||
const clamp=(v,a,b)=>Math.min(b,Math.max(a,v));
|
||
function vwClamp(){ const w=vw.fw*vw.s,h=vw.fh*vw.s;
|
||
vw.tx=w<=vw.cw?(vw.cw-w)/2:clamp(vw.tx,vw.cw-w,0); vw.ty=h<=vw.ch?(vw.ch-h)/2:clamp(vw.ty,vw.ch-h,0); }
|
||
function vwApply(){ vwClamp(); vEl.st.style.transform=`translate(${vw.tx}px,${vw.ty}px) scale(${vw.s})`; vEl.st.style.setProperty("--k",1/vw.s); }
|
||
function vwLayout(){ vw.cw=vEl.cv.clientWidth; vw.ch=vEl.cv.clientHeight; if(!vw.nw||!vw.cw) return;
|
||
const base=Math.min(vw.cw/vw.nw,vw.ch/vw.nh); vw.fw=vw.nw*base; vw.fh=vw.nh*base;
|
||
vEl.st.style.width=vw.fw+"px"; vEl.st.style.height=vw.fh+"px"; vwApply(); }
|
||
function vwZoomAt(ns,cx,cy){ ns=clamp(ns,1,8); const px=(cx-vw.tx)/vw.s, py=(cy-vw.ty)/vw.s; vw.s=ns; vw.tx=cx-px*ns; vw.ty=cy-py*ns; vwApply(); }
|
||
function vwZoomCentre(f){ vwZoomAt(vw.s*f,vw.cw/2,vw.ch/2); }
|
||
function vwFocusPin(r){ const pl=(pinsFor(current)[r]||[])[0]; if(!pl) return;
|
||
const ns=Math.max(vw.s,3); const px=pl[0]/100*vw.fw, py=pl[1]/100*vw.fh; vw.s=clamp(ns,1,8); vw.tx=vw.cw/2-px*vw.s; vw.ty=vw.ch/2-py*vw.s; vwApply(); }
|
||
function vwLoadImage(src,white,after){
|
||
vw.nw=0; vEl.st.hidden=true; vw.s=1;
|
||
const im=new Image(); im.alt=""; im.draggable=false;
|
||
im.onload=()=>{ vw.nw=im.naturalWidth; vw.nh=im.naturalHeight;
|
||
vEl.st.querySelector("img")?.remove(); vEl.st.prepend(im); vEl.st.classList.toggle("white",!!white);
|
||
vEl.st.hidden=false; vwLayout(); if(after) after(); };
|
||
im.onerror=()=>{ toast("Couldn’t load this image (offline and not saved yet?)"); };
|
||
im.src=src;
|
||
}
|
||
function vwPinsHtml(){
|
||
const G=GROUPS.find(x=>x.key===current); const refset=new Set(G.refs.map(r=>r.r));
|
||
return Object.entries(pinsFor(current)).flatMap(([r,list])=>list.map((p,i)=>{
|
||
const orphan=!refset.has(r); const st=orphan?"none":statusOf(current,r);
|
||
return `<button class="pin s-${st}${selRef===r?" sel":""}${orphan?" orphan":""}" style="left:${p[0]}%;top:${p[1]}%" data-vr="${r}" aria-label="Part ${r}">${selRef===r&&i===0?`<span class="lab">${r}</span>`:""}</button>`; })).join("");
|
||
}
|
||
function vwDrawingSheet(){
|
||
const G=GROUPS.find(x=>x.key===current); const pins=pinsFor(current);
|
||
const refs=G.refs.filter(r=>pins[r.r]);
|
||
const chips=refs.map(r=>`<button class="vchip s-${statusOf(current,r.r)}${selRef===r.r?" sel":""}" data-vr="${r.r}" data-fly="1">${r.r}</button>`).join("");
|
||
const R=selRef&&G.refs.find(r=>r.r===selRef);
|
||
const sel=R?`<div class="vsel"><div class="nm"><span class="mono">#${R.r}</span> ${esc(bestName(R))}</div>
|
||
<div class="sbtns">${Object.entries(STATUS).map(([k,l])=>`<button data-vs="${k}" aria-pressed="${statusOf(current,R.r)===k}" data-s="${k}">${l}</button>`).join("")}</div>
|
||
<div class="tools"><button class="btn" data-v="details">Notes, links & photos</button><a class="btn" target="_blank" rel="noopener" href="https://www.oemmotorparts.com/en/search?filter=${encodeURIComponent(R.v[0].o)}">Find part</a></div></div>`
|
||
:`<div class="rsub">Tap a marker on the drawing, or a number below, to select a part. Pinch or double-tap to zoom.</div>`;
|
||
vEl.bt.innerHTML=`${sel}<div class="vchips">${chips||'<span class="rsub">No markers on this drawing yet.</span>'}</div>`;
|
||
vEl.st.querySelectorAll(".pin").forEach(n=>n.remove()); vEl.st.insertAdjacentHTML("beforeend",vwPinsHtml());
|
||
vEl.ti.textContent=G.title;
|
||
}
|
||
function vwPhotoSheet(){
|
||
const p=vw.list[vw.i]; if(!p) return;
|
||
vEl.ti.textContent=`${vw.i+1} / ${vw.list.length}`;
|
||
vEl.bt.innerHTML=`<div class="cap">${esc(p.label||"")}${p.label&&p.caption?" · ":""}${esc(p.caption||"")}${!p.label&&!p.caption?'<span class="rsub">No caption</span>':""}${p.at?` <span class="rsub">· ${when(p.at)}</span>`:""}</div>
|
||
${p.pending?'<div class="rsub">Waiting to upload.</div>':`<div class="tools"><button class="btn" data-v="cap">Edit caption</button><button class="btn" data-v="del">Delete photo</button></div>`}`;
|
||
vEl.pv.hidden=vEl.nx.hidden=vw.list.length<2;
|
||
}
|
||
function vwShowPhoto(){ const p=vw.list[vw.i]; vwLoadImage(p.src,false); vwPhotoSheet(); }
|
||
function openViewer(){
|
||
Object.assign(vEl,{root:$("#viewer"),cv:$("#vcanvas"),st:$("#vstage"),bt:$("#vbottom"),ti:$("#vtitle"),pv:document.querySelector(".vnav.prev"),nx:document.querySelector(".vnav.next")});
|
||
if(!vEl.ro){ vEl.ro=new ResizeObserver(()=>vwLayout()); vEl.ro.observe(vEl.cv); }
|
||
vEl.root.hidden=false; vw.open=true; document.body.style.overflow="hidden";
|
||
try{ history.pushState({v:1},""); }catch(_){}
|
||
}
|
||
function openDrawing(g){ const img=imageFor(g); if(!img) return; vw.mode="drawing"; openViewer(); vEl.st.querySelectorAll(".pin").forEach(n=>n.remove());
|
||
vEl.pv.hidden=vEl.nx.hidden=true; vwLoadImage(img,true,()=>{ vwDrawingSheet(); }); vwDrawingSheet(); }
|
||
function openPhotos(list,i){ if(!list.length) return; vw.mode="photos"; vw.list=list; vw.i=clamp(i,0,list.length-1); openViewer(); vEl.st.querySelectorAll(".pin").forEach(n=>n.remove()); vwShowPhoto(); }
|
||
function hideViewer(){ if(!vw.open) return; vw.open=false; vEl.root.hidden=true; document.body.style.overflow=""; vEl.st.innerHTML=""; }
|
||
function closeViewer(){ if(history.state&&history.state.v){ history.back(); } else hideViewer(); }
|
||
window.addEventListener("popstate",()=>hideViewer());
|
||
function vwSelect(r,fly){ selRef=r; filter="all"; vwDrawingSheet(); if(fly) vwFocusPin(r); render(); }
|
||
function vwStep(d){ if(vw.mode!=="photos"||vw.list.length<2) return; vw.i=(vw.i+d+vw.list.length)%vw.list.length; vwShowPhoto(); }
|
||
async function vwDelete(){
|
||
const p=vw.list[vw.i]; if(!p||p.id<0||!confirm("Delete this photo for everyone?")) return;
|
||
try{ const r=await send("DELETE","api/photos/"+p.id,undefined,()=>{ for(const k in state.photos) state.photos[k]=state.photos[k].filter(x=>x.id!==p.id); });
|
||
vw.list.splice(vw.i,1); if(!vw.list.length){ closeViewer(); } else { vw.i=Math.min(vw.i,vw.list.length-1); vwShowPhoto(); }
|
||
if(!r.queued) await reload(); else render(); }
|
||
catch(e){ toast("Couldn’t delete: "+e.message); }
|
||
}
|
||
async function vwCaption(){
|
||
const p=vw.list[vw.i]; const c=prompt("Caption",p.caption||""); if(c===null) return;
|
||
try{ const r=await send("PUT","api/photos/"+p.id,{caption:c},()=>{ const q=(state.photos[p.scope]||[]).find(x=>x.id===p.id); if(q) q.caption=c; });
|
||
p.caption=c; vwPhotoSheet(); if(!r.queued) await reload(); else render(); }
|
||
catch(e){ toast("Couldn’t save: "+e.message); }
|
||
}
|
||
document.addEventListener("click",async e=>{
|
||
const v=e.target.closest("#viewer"); if(!v) return; e.stopPropagation();
|
||
if(vw.moved){ return; }
|
||
const b=e.target.closest("button"); if(!b) return;
|
||
if(b.dataset.vr){ vwSelect(b.dataset.vr,!!b.dataset.fly); return; }
|
||
if(b.dataset.vs){ const R=selRef; const cur=statusOf(current,R); await savePart(current,R,{s:cur===b.dataset.vs?"none":b.dataset.vs}); vwDrawingSheet(); return; }
|
||
switch(b.dataset.v){
|
||
case "close": closeViewer(); break;
|
||
case "zin": vwZoomCentre(1.6); break;
|
||
case "zout": vwZoomCentre(1/1.6); break;
|
||
case "fit": vw.s=1; vwApply(); break;
|
||
case "prev": vwStep(-1); break;
|
||
case "next": vwStep(1); break;
|
||
case "del": vwDelete(); break;
|
||
case "cap": vwCaption(); break;
|
||
case "details": { const r=selRef; closeViewer(); setTimeout(()=>{ selRef=r; render(); },50); break; }
|
||
}
|
||
},true);
|
||
(function(){
|
||
const cv=document.getElementById("vcanvas"); const ptrs=new Map(); let g=null, lastTap=0, lastX=0, lastY=0;
|
||
const pt=e=>{ const r=cv.getBoundingClientRect(); return {x:e.clientX-r.left,y:e.clientY-r.top}; };
|
||
const begin=()=>{ const a=[...ptrs.values()];
|
||
if(a.length===1) g={t:"pan",x:a[0].x,y:a[0].y,tx:vw.tx,ty:vw.ty,sx:a[0].x,sy:a[0].y};
|
||
else if(a.length>=2){ g={t:"pinch",d:Math.hypot(a[0].x-a[1].x,a[0].y-a[1].y)||1,s:vw.s,mx:(a[0].x+a[1].x)/2,my:(a[0].y+a[1].y)/2,tx:vw.tx,ty:vw.ty}; } else g=null; };
|
||
cv.addEventListener("pointerdown",e=>{ if(e.target.closest(".vnav")) return; ptrs.set(e.pointerId,pt(e)); vw.moved=false; begin(); });
|
||
window.addEventListener("pointermove",e=>{ if(!ptrs.has(e.pointerId)||!g) return; const p=pt(e); ptrs.set(e.pointerId,p); const a=[...ptrs.values()];
|
||
if(g.t==="pan"&&a.length===1){ const dx=p.x-g.sx, dy=p.y-g.sy; if(Math.abs(dx)+Math.abs(dy)>8) vw.moved=true; vw.tx=g.tx+(p.x-g.x); vw.ty=g.ty+(p.y-g.y); vwApply(); }
|
||
else if(g.t==="pinch"&&a.length>=2){ vw.moved=true; const d=Math.hypot(a[0].x-a[1].x,a[0].y-a[1].y), mx=(a[0].x+a[1].x)/2, my=(a[0].y+a[1].y)/2;
|
||
const ns=clamp(g.s*d/g.d,1,8); const px=(g.mx-g.tx)/g.s, py=(g.my-g.ty)/g.s; vw.s=ns; vw.tx=mx-px*ns; vw.ty=my-py*ns; vwApply(); } });
|
||
const up=e=>{ if(!ptrs.has(e.pointerId)) return; const p=ptrs.get(e.pointerId); ptrs.delete(e.pointerId);
|
||
if(g&&g.t==="pan"&&ptrs.size===0){
|
||
const dx=p.x-g.sx, dy=p.y-g.sy;
|
||
if(vw.mode==="photos"&&vw.s<1.05&&Math.abs(dx)>70&&Math.abs(dx)>Math.abs(dy)*1.5){ vwStep(dx<0?1:-1); }
|
||
else if(!vw.moved&&e.pointerType==="touch"&&!e.target.closest(".pin")){ const now=Date.now();
|
||
if(now-lastTap<320&&Math.hypot(p.x-lastX,p.y-lastY)<30){ vwZoomAt(vw.s>1.2?1:2.8,p.x,p.y); lastTap=0; } else { lastTap=now; lastX=p.x; lastY=p.y; } }
|
||
}
|
||
begin(); setTimeout(()=>{ if(!ptrs.size) vw.moved=false; },60); };
|
||
window.addEventListener("pointerup",up); window.addEventListener("pointercancel",up);
|
||
cv.addEventListener("wheel",e=>{ e.preventDefault(); const p=pt(e); vwZoomAt(vw.s*Math.exp(-e.deltaY*0.0018),p.x,p.y); },{passive:false});
|
||
cv.addEventListener("dblclick",e=>{ if(e.target.closest(".pin")) return; const p=pt(e); vwZoomAt(vw.s>1.2?1:2.8,p.x,p.y); });
|
||
window.addEventListener("keydown",e=>{ if(!vw.open) return; if(e.key==="Escape") closeViewer(); if(e.key==="ArrowLeft") vwStep(-1); if(e.key==="ArrowRight") vwStep(1); if(e.key==="+"||e.key==="=") vwZoomCentre(1.4); if(e.key==="-") vwZoomCentre(1/1.4); });
|
||
})();
|
||
|
||
// ---------- boot ----------
|
||
try{ const t=localStorage.getItem(LS+"-tab"); if(t && (t==="shop"||t==="gallery"||t==="spending"||t==="activity"||GROUPS.some(g=>g.key===t))) current=t; }catch(_){}
|
||
if("serviceWorker" in navigator) navigator.serviceWorker.register("sw.js").catch(()=>{});
|
||
window.addEventListener("online",()=>reload());
|
||
window.addEventListener("offline",()=>{ online=false; badgeNow(); });
|
||
render();
|
||
(async()=>{ try{ const c=await kvGet("state"); if(c&&!online) state=Object.assign(EMPTY(),c); pendingN=(await qAll()).length; }catch(_){} render(); reload(); })();
|
||
setInterval(()=>{
|
||
if(document.hidden||!navigator.onLine) return;
|
||
const typing=document.activeElement&&document.activeElement.matches("textarea,input");
|
||
if(typing||vw.open||drag) api("GET","api/ping").catch(()=>{}); else reload();
|
||
},30000);
|
||
// pick up changes made on another device when you come back to the tab
|
||
document.addEventListener("visibilitychange",()=>{ if(!document.hidden && !drag && !document.activeElement.matches("textarea,input")) reload(); });
|
||
</script>
|
||
</body>
|
||
</html>
|