{"id":11,"date":"2025-10-18T09:22:26","date_gmt":"2025-10-18T09:22:26","guid":{"rendered":"https:\/\/harrylarsen.com\/?page_id=11"},"modified":"2025-10-18T09:25:58","modified_gmt":"2025-10-18T09:25:58","slug":"11-2","status":"publish","type":"page","link":"https:\/\/harrylarsen.com\/index.php\/11-2\/","title":{"rendered":"pool"},"content":{"rendered":"<p><!doctype html><br \/>\n<html lang=\"no\"><br \/>\n<head><br \/>\n  <meta charset=\"utf-8\" \/><br \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/><br \/>\n  <title>Biljardturnering \u2013 Dobbelt utslag (16)<\/title><br \/>\n  <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script><\/p>\n<style>\n    html, body { height: 100%; }\n    body { background: #111; }\n    \/* Smooth scrolling columns *\/\n    .scroll-snap-x { scroll-snap-type: x proximity; }\n    .scroll-snap-child { scroll-snap-align: start; }\n  <\/style>\n<p><\/head><br \/>\n<body><\/p>\n<div id=\"root\"><\/div>\n<p>  <!-- React 18 + ReactDOM --><br \/>\n  <script crossorigin src=\"https:\/\/unpkg.com\/react@18\/umd\/react.development.js\"><\/script><br \/>\n  <script crossorigin src=\"https:\/\/unpkg.com\/react-dom@18\/umd\/react-dom.development.js\"><\/script><br \/>\n  <!-- Babel for JSX in the browser (ok for prototypes) --><br \/>\n  <script src=\"https:\/\/unpkg.com\/@babel\/standalone\/babel.min.js\"><\/script><\/p>\n<p>  <script type=\"text\/babel\">\n    const { useMemo, useState } = React;<\/p>\n<p>    \/\/ ------------------------------------------------------------\n    \/\/ Webbasert biljardturnering \u2013 Dobbelt utslag (16 spillere)\n    \/\/ Standalone React i ett HTML-dokument (CDN-baserte biblioteker)\n    \/\/ ------------------------------------------------------------<\/p>\n<p>    function makeMatch(id, label) {\n      return { id, label, p1: null, p2: null, winner: null, loser: null };\n    }<\/p>\n<p>    function App() {\n      const [started, setStarted] = useState(false);\n      const [fullscreen, setFullscreen] = useState(false);\n      const [bestOf, setBestOf] = useState(1);\n      const [players, setPlayers] = useState(\n        Array.from({ length: 16 }, (_, i) => ({ id: i + 1, name: `Spiller ${i + 1}` }))\n      );<\/p>\n<p>      const updatePlayerName = (idx, name) => {\n        setPlayers(prev => prev.map((p, i) => i === idx ? { ...p, name } : p));\n      };<\/p>\n<p>      \/\/ Winner Bracket\n      const wb = useMemo(() => ({\n        r1: [1,2,3,4,5,6,7,8].map(i => makeMatch(`M${i}`, `WB R1 \u00b7 M${i}`)),\n        r2: [9,10,11,12].map(i => makeMatch(`M${i}`, `WB R2 \u00b7 M${i}`)),\n        r3: [13,14].map(i => makeMatch(`M${i}`, `WB R3 \u00b7 M${i}`)),\n        r4: [15].map(i => makeMatch(`M${i}`, `WB Final \u00b7 M${i}`)),\n      }), []);<\/p>\n<p>      \/\/ Loser Bracket\n      const lb = useMemo(() => ({\n        r1: [1,2,3,4].map(i => makeMatch(`L${i}`, `LB R1 \u00b7 L${i}`)),\n        r2: [5,6,7,8].map(i => makeMatch(`L${i}`, `LB R2 \u00b7 L${i}`)),\n        r3: [9,10].map(i => makeMatch(`L${i}`, `LB R3 \u00b7 L${i}`)),\n        r4: [11,12].map(i => makeMatch(`L${i}`, `LB R4 \u00b7 L${i}`)),\n        r5: [13].map(i => makeMatch(`L${i}`, `LB R5 \u00b7 L${i}`)),\n        r6: [14].map(i => makeMatch(`L${i}`, `LB Final \u00b7 L${i}`)),\n        gf: [makeMatch('GF1', 'Grand Final \u00b7 GF1'), makeMatch('GF2', 'Grand Final Reset \u00b7 GF2')],\n      }), []);<\/p>\n<p>      const allMatchesInit = useMemo(() => {\n        const map = {};\n        [...wb.r1, ...wb.r2, ...wb.r3, ...wb.r4,\n         ...lb.r1, ...lb.r2, ...lb.r3, ...lb.r4, ...lb.r5, ...lb.r6, ...lb.gf]\n         .forEach(m => { map[m.id] = m; });\n        return map;\n      }, [wb, lb]);<\/p>\n<p>      const [matches, setMatches] = useState(allMatchesInit);<\/p>\n<p>      const seedingPairs = [\n        [1,16], [8,9], [5,12], [4,13], [3,14], [6,11], [7,10], [2,15]\n      ];<\/p>\n<p>      const startTournament = () => {\n        const next = structuredClone(matches);\n        wb.r1.forEach((m, idx) => {\n          const [a, b] = seedingPairs[idx];\n          next[m.id].p1 = players.find(p => p.id === a);\n          next[m.id].p2 = players.find(p => p.id === b);\n        });\n        setMatches(next);\n        setStarted(true);\n      };<\/p>\n<p>      const routing = useMemo(() => ({\n        M1:  { win: { to: 'M9',  slot: 1 },  lose: { to: 'L1', slot: 1 } },\n        M2:  { win: { to: 'M9',  slot: 2 },  lose: { to: 'L1', slot: 2 } },\n        M3:  { win: { to: 'M10', slot: 1 },  lose: { to: 'L2', slot: 1 } },\n        M4:  { win: { to: 'M10', slot: 2 },  lose: { to: 'L2', slot: 2 } },\n        M5:  { win: { to: 'M11', slot: 1 },  lose: { to: 'L3', slot: 1 } },\n        M6:  { win: { to: 'M11', slot: 2 },  lose: { to: 'L3', slot: 2 } },\n        M7:  { win: { to: 'M12', slot: 1 },  lose: { to: 'L4', slot: 1 } },\n        M8:  { win: { to: 'M12', slot: 2 },  lose: { to: 'L4', slot: 2 } },\n        M9:  { win: { to: 'M13', slot: 1 },  lose: { to: 'L5', slot: 2 } },\n        M10: { win: { to: 'M13', slot: 2 },  lose: { to: 'L6', slot: 2 } },\n        M11: { win: { to: 'M14', slot: 1 },  lose: { to: 'L7', slot: 2 } },\n        M12: { win: { to: 'M14', slot: 2 },  lose: { to: 'L8', slot: 2 } },\n        M13: { win: { to: 'M15', slot: 1 },  lose: { to: 'L11', slot: 2 } },\n        M14: { win: { to: 'M15', slot: 2 },  lose: { to: 'L12', slot: 2 } },\n        M15: { win: { to: 'GF1', slot: 1 },  lose: { to: 'L14', slot: 2 } },\n        L1: { win: { to: 'L5', slot: 1 } },\n        L2: { win: { to: 'L6', slot: 1 } },\n        L3: { win: { to: 'L7', slot: 1 } },\n        L4: { win: { to: 'L8', slot: 1 } },\n        L5: { win: { to: 'L9',  slot: 1 } },\n        L6: { win: { to: 'L9',  slot: 2 } },\n        L7: { win: { to: 'L10', slot: 1 } },\n        L8: { win: { to: 'L10', slot: 2 } },\n        L9:  { win: { to: 'L11', slot: 1 } },\n        L10: { win: { to: 'L12', slot: 1 } },\n        L11: { win: { to: 'L13', slot: 1 } },\n        L12: { win: { to: 'L13', slot: 2 } },\n        L13: { win: { to: 'L14', slot: 1 } },\n        L14: { win: { to: 'GF1', slot: 2 } },\n        GF1: { win: { to: null }, lose: { to: 'GF2', slot: 1 } },\n        GF2: { win: { to: null } },\n      }), []);<\/p>\n<p>      const setWinner = (matchId, winnerIdx) => {\n        setMatches(prev => {\n          const next = structuredClone(prev);\n          const m = next[matchId];\n          const winner = winnerIdx === 1 ? m.p1 : m.p2;\n          const loser = winnerIdx === 1 ? m.p2 : m.p1;\n          if (!winner || !loser) return prev;\n          m.winner = winner; m.loser = loser;<\/p>\n<p>          const route = routing[matchId];\n          if (route?.win?.to) {\n            const t = next[route.win.to];\n            if (t) { (route.win.slot === 1) ? (t.p1 = winner) : (t.p2 = winner); }\n          }\n          if (route?.lose?.to) {\n            const t = next[route.lose.to];\n            if (t) { (route.lose.slot === 1) ? (t.p1 = loser) : (t.p2 = loser); }\n          }<\/p>\n<p>          if (matchId === 'GF1' && m.winner && m.loser) {\n            const gf2 = next['GF2'];\n            if (gf2) { gf2.p1 = m.p1; gf2.p2 = m.p2; }\n          }<\/p>\n<p>          return next;\n        });\n      };<\/p>\n<p>      const Header = () => (<\/p>\n<div className=\"flex items-center justify-between mb-4\">\n<div className=\"flex items-center gap-3\">\n<div className=\"w-8 h-8 rounded-full bg-yellow-500 shadow\" \/>\n<h1 className=\"text-xl md:text-2xl font-bold text-white\">Biljardturnering \u00b7 Dobbelt utslag (16)<\/h1>\n<\/p><\/div>\n<div className=\"flex items-center gap-2\">\n            <label className=\"text-sm text-gray-200 hidden md:block\">Best av<\/label>\n            <select\n              className=\"px-2 py-1 bg-gray-800 text-gray-100 rounded\"\n              value={bestOf}\n              onChange={(e) => setBestOf(parseInt(e.target.value))}\n              disabled={started}\n            ><option value={1}>1<\/option><option value={3}>3<\/option><option value={5}>5<\/option><\/select>\n            <button\n              className={`px-3 py-1 rounded ${fullscreen ? 'bg-gray-700' : 'bg-blue-600 hover:bg-blue-500'} text-white`}\n              onClick={() => setFullscreen(f => !f)}\n            >{fullscreen ? 'Normal' : 'Storskjerm'}<\/button>\n          <\/div>\n<\/p><\/div>\n<p>      );<\/p>\n<p>      const PreStart = () => (<\/p>\n<div className=\"bg-gray-900\/60 p-4 rounded-2xl shadow border border-gray-800\">\n<h2 className=\"text-lg font-semibold text-white mb-2\">Deltakere<\/h2>\n<p className=\"text-gray-300 text-sm mb-4\">Rediger navnene og trykk \u00abStart turnering\u00bb. Seeding er 1\u201316.<\/p>\n<div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2\">\n            {players.map((p, i) => (<\/p>\n<div key={p.id} className=\"flex items-center gap-2 bg-gray-800\/60 rounded-xl p-2\">\n<div className=\"w-6 h-6 rounded-full bg-white shadow-inner flex items-center justify-center text-xs font-bold text-gray-900\">{p.id}<\/div>\n<p>                <input\n                  className=\"w-full bg-gray-900 text-gray-100 rounded px-2 py-1 outline-none border border-gray-700 focus:border-blue-500\"\n                  value={p.name}\n                  onChange={e => updatePlayerName(i, e.target.value)}\n                \/>\n              <\/div>\n<p>            ))}\n          <\/p><\/div>\n<div className=\"mt-4 flex gap-2\">\n            <button onClick={startTournament} className=\"px-4 py-2 rounded-xl bg-yellow-500 hover:bg-yellow-400 text-gray-900 font-semibold\">Start turnering<\/button>\n            <button onClick={() => { setPlayers(Array.from({ length: 16 }, (_, i) => ({ id: i + 1, name: `Spiller ${i + 1}` })); setMatches(allMatchesInit); }} className=\"px-4 py-2 rounded-xl bg-gray-800 text-gray-200 border border-gray-700\">Nullstill<\/button>\n          <\/div>\n<\/p><\/div>\n<p>      );<\/p>\n<p>      const MatchCard = ({ m }) => {\n        const ready = m.p1 && m.p2;\n        const decided = !!m.winner;\n        const name1 = m.p1?.name || '\u2014';\n        const name2 = m.p2?.name || '\u2014';\n        return (<\/p>\n<div className={`rounded-xl p-3 border ${decided ? 'border-green-500\/60' : 'border-gray-700'} bg-gray-900\/60`}>\n<div className=\"text-xs text-gray-300 mb-2\">{m.label}<\/div>\n<div className=\"flex flex-col gap-2\">\n              <button\n                disabled={!ready}\n                onClick={() => setWinner(m.id, 1)}\n                className={`w-full text-left px-3 py-2 rounded-lg ${decided && m.winner?.id === m.p1?.id ? 'bg-green-700 text-white' : 'bg-gray-800 text-gray-100 hover:bg-gray-700'} disabled:opacity-40`}\n              >{name1}<\/button>\n              <button\n                disabled={!ready}\n                onClick={() => setWinner(m.id, 2)}\n                className={`w-full text-left px-3 py-2 rounded-lg ${decided && m.winner?.id === m.p2?.id ? 'bg-green-700 text-white' : 'bg-gray-800 text-gray-100 hover:bg-gray-700'} disabled:opacity-40`}\n              >{name2}<\/button>\n            <\/div>\n<p>            {decided && (<\/p>\n<div className=\"mt-2 text-xs text-gray-300\">Vinner: <span className=\"text-white font-semibold\">{m.winner?.name}<\/span><\/div>\n<p>            )}\n          <\/p><\/div>\n<p>        );\n      };<\/p>\n<p>      const Column = ({ title, children }) => (<\/p>\n<div className=\"min-w-[220px] w-full lg:w-auto scroll-snap-child\">\n<div className=\"text-sm font-semibold text-gray-200 mb-2\">{title}<\/div>\n<div className=\"flex flex-col gap-3\">{children}<\/div>\n<\/p><\/div>\n<p>      );<\/p>\n<p>      return (<\/p>\n<div className={`${fullscreen ? 'p-3' : 'p-4 md:p-6'} min-h-screen text-gray-100 font-sans`}>\n          <Header \/>\n          {!started ? (\n            <PreStart \/>\n          ) : (<\/p>\n<div className=\"flex flex-col gap-4\">\n<div className=\"grid grid-cols-1 xl:grid-cols-2 gap-4\">\n<div className=\"bg-gray-950\/60 rounded-2xl p-4 border border-gray-800\">\n<div className=\"flex items-center gap-2 mb-3\">\n<div className=\"w-3 h-3 rounded-full bg-yellow-500\" \/>\n<h2 className=\"font-bold\">Winner Bracket<\/h2>\n<\/p><\/div>\n<div className=\"flex gap-3 overflow-x-auto scroll-snap-x\">\n                    <Column title=\"Runde 1\">{wb.r1.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 2\">{wb.r2.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 3\">{wb.r3.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"WB-Finale\">{wb.r4.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                  <\/div>\n<\/p><\/div>\n<div className=\"bg-gray-950\/60 rounded-2xl p-4 border border-gray-800\">\n<div className=\"flex items-center gap-2 mb-3\">\n<div className=\"w-3 h-3 rounded-full bg-blue-500\" \/>\n<h2 className=\"font-bold\">Loser Bracket<\/h2>\n<\/p><\/div>\n<div className=\"flex gap-3 overflow-x-auto scroll-snap-x\">\n                    <Column title=\"Runde 1\">{lb.r1.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 2\">{lb.r2.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 3\">{lb.r3.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 4\">{lb.r4.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"Runde 5\">{lb.r5.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                    <Column title=\"LB-Finale\">{lb.r6.map(m => <MatchCard key={m.id} m={matches[m.id]} \/>)}<\/Column>\n                  <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div className=\"bg-gray-950\/60 rounded-2xl p-4 border border-gray-800\">\n<div className=\"flex items-center gap-2 mb-3\">\n<div className=\"w-3 h-3 rounded-full bg-orange-500\" \/>\n<h2 className=\"font-bold\">Grand Final<\/h2>\n<\/p><\/div>\n<div className=\"flex gap-3 overflow-x-auto scroll-snap-x\">\n                  <Column title=\"GF1\"><MatchCard m={matches['GF1']} \/><\/Column>\n                  <Column title=\"Reset (ved behov)\"><MatchCard m={matches['GF2']} \/><\/Column>\n                <\/div>\n<\/p><\/div>\n<div className=\"text-xs text-gray-300\">\n<p className=\"mb-1\">Tips: Klikk p\u00e5 et navn i en kamp for \u00e5 registrere vinner. Vinnere\/tapere flyttes automatisk til neste kamp.<\/p>\n<p>Best-av-innstillingen er for praktisk gjennomf\u00f8ring og kan bygges ut med sett\/frames senere.<\/p>\n<\/p><\/div>\n<\/p><\/div>\n<p>          )}\n        <\/p><\/div>\n<p>      );\n    }<\/p>\n<p>    const root = ReactDOM.createRoot(document.getElementById('root'));\n    root.render(<App \/>);\n  <\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Biljardturnering \u2013 Dobbelt utslag (16)<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-11","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/pages\/11","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/comments?post=11"}],"version-history":[{"count":2,"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/pages\/11\/revisions"}],"predecessor-version":[{"id":13,"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/pages\/11\/revisions\/13"}],"wp:attachment":[{"href":"https:\/\/harrylarsen.com\/index.php\/wp-json\/wp\/v2\/media?parent=11"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}