{"id":15708,"date":"2026-10-16T20:53:49","date_gmt":"2026-10-16T18:53:49","guid":{"rendered":"https:\/\/www.ok3d.fr\/?page_id=15708"},"modified":"2026-10-16T20:54:29","modified_gmt":"2026-10-16T18:54:29","slug":"palette-studio","status":"publish","type":"page","link":"https:\/\/www.ok3d.fr\/en\/palette-studio\/","title":{"rendered":"Palette studio"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"15708\" class=\"elementor elementor-15708\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bbe5d3c e-con e-atomic-element e-flexbox-base e-ba436fd \" data-id=\"bbe5d3c\" data-element_type=\"e-flexbox\" data-e-type=\"e-flexbox\" data-interaction-id=\"bbe5d3c\" data-e-type=\"e-flexbox\" data-id=\"bbe5d3c\">\n    \t\t<div class=\"elementor-element elementor-element-c1d60fd elementor-widget elementor-widget-html\" data-id=\"c1d60fd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!-- Palette Studio by OK3D \u2014 contenu centr\u00e9, sans en-t\u00eate int\u00e9gr\u00e9.\n  Coller TOUT ce fichier dans un widget HTML (sans balises html\/head\/body).\n  Page : Elementor pleine largeur pour conserver l'en-t\u00eate du site.\n  Conteneur : pleine largeur, marges\/padding 0,\n  d\u00e9bordement visible. Le contenu conserve son d\u00e9filement vertical.\n  Cadre maximal : 1600px, marges comprises, comme le configurateur OK3D.\n  Header : 94px + 54px d'espace sur ordinateur ; 78px + 43px sur mobile.\n  R\u00e9glages : --okk-content-max, --okk-header-offset et --okk-top-gap.\n-->\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link rel=\"stylesheet\" href=\"https:\/\/fonts.googleapis.com\/css2?family=Unbounded:wght@500;700;800&family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@500;600&display=swap\">\n<style>\n.okk-scope[data-okk=\"palette\"]{display:block;position:relative;left:auto;width:100vw!important;max-width:100vw!important;margin-left:calc(50% - 50vw)!important;margin-right:calc(50% - 50vw)!important;font-family:var(--f-body);font-size:15px;line-height:1.5;color:var(--ink);background:var(--bg);text-align:left;-webkit-font-smoothing:antialiased}\n.okk-scope[data-okk=\"palette\"] *,.okk-scope[data-okk=\"palette\"] *::before,.okk-scope[data-okk=\"palette\"] *::after{box-sizing:border-box}\n.okk-scope[data-okk=\"palette\"] [hidden]{display:none!important}\n.okk-scope[data-okk=\"palette\"] > .okk{min-height:100vh;min-height:100svh}\n.okk-scope[data-okk=\"palette\"] img{max-width:100%;height:auto}\n.okk-scope[data-okk=\"palette\"] button,.okk-scope[data-okk=\"palette\"] input{font-family:inherit;text-transform:none;letter-spacing:normal;min-height:0;box-shadow:none;margin:0}\n.okk-scope[data-okk=\"palette\"] h1,.okk-scope[data-okk=\"palette\"] h2,.okk-scope[data-okk=\"palette\"] h3{color:inherit;text-transform:none;padding:0}\n.okk-scope[data-okk=\"palette\"] p{margin:0}\n.okk-scope[data-okk=\"palette\"] a{text-decoration:none}\n.okk-scope[data-okk=\"palette\"]{\n  color-scheme:dark;\n  --bg:#06080C; --ink:#EEF2F8; --muted:#8792A6; --faint:#566074; --line:#1C2433; --line2:#2A3549;\n  --brand:#2E6BFF; --brand-hi:#4F86FF; --brand2:#7CC4FF; --glow:rgba(46,107,255,.45); --good:#35E0A1;\n  --f-display:\"Unbounded\",\"Arial Black\",system-ui,sans-serif; --f-body:\"Geist\",system-ui,-apple-system,\"Segoe UI\",sans-serif; --f-mono:\"Geist Mono\",ui-monospace,Menlo,Consolas,monospace;\n}\n.okk-scope[data-okk=\"palette\"]{background:var(--bg);color:var(--ink)}\n.okk-scope[data-okk=\"palette\"]{font-family:var(--f-body);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased}\n.okk-scope[data-okk=\"palette\"] *{box-sizing:border-box}\n.okk-scope[data-okk=\"palette\"] button{font:inherit;color:inherit;cursor:pointer;background:none;border:0;padding:0}\n.okk-scope[data-okk=\"palette\"] a{color:inherit}\n.okk-scope[data-okk=\"palette\"] a.btn{text-decoration:none}\n.okk-scope[data-okk=\"palette\"] :focus-visible{outline:2px solid var(--brand2);outline-offset:3px;border-radius:6px}\n.okk-scope[data-okk=\"palette\"] .okk{position:relative;overflow:hidden;min-height:100%;padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));padding-block:clamp(14px,3vw,34px) 48px;\n  background:radial-gradient(900px 500px at 85% -10%,var(--amb,rgba(46,107,255,.22)),transparent 62%),radial-gradient(800px 480px at 0% 10%,var(--amb2,rgba(124,196,255,.08)),transparent 60%),var(--bg);transition:background .8s}\n.okk-scope[data-okk=\"palette\"] .wrap{position:relative;max-width:1280px;margin:0 auto}\n.okk-scope[data-okk=\"palette\"] .eyebrow{font:600 11px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--brand2);display:inline-flex;align-items:center;gap:8px}\n.okk-scope[data-okk=\"palette\"] .eyebrow::before{content:\"\";width:6px;height:6px;border-radius:2px;background:var(--brand2);box-shadow:0 0 10px var(--brand2)}\n.okk-scope[data-okk=\"palette\"] h1,.okk-scope[data-okk=\"palette\"] h2,.okk-scope[data-okk=\"palette\"] h3{margin:0;font-family:var(--f-display);letter-spacing:-.035em;text-wrap:balance}\n.okk-scope[data-okk=\"palette\"] .btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;height:46px;padding:0 22px;border-radius:999px;font-weight:600;font-size:14.5px;transition:transform .15s,border-color .2s;white-space:nowrap}\n.okk-scope[data-okk=\"palette\"] .btn.pri{background:linear-gradient(180deg,var(--brand-hi),#2152E0);color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.28),0 12px 30px -10px var(--glow),0 0 0 1px rgba(46,107,255,.6)}\n.okk-scope[data-okk=\"palette\"] .btn.pri:hover{transform:translateY(-1px)}\n.okk-scope[data-okk=\"palette\"] .btn.gho{border:1px solid var(--line2);background:rgba(255,255,255,.02)}\n.okk-scope[data-okk=\"palette\"] .btn.gho:hover{border-color:var(--muted)}\n.okk-scope[data-okk=\"palette\"] .btn.sm{height:36px;padding:0 14px;font-size:13px}\n.okk-scope[data-okk=\"palette\"] .glass{background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,.012));border:1px solid var(--line);border-radius:26px;box-shadow:inset 0 1px 0 rgba(255,255,255,.045)}\n.okk-scope[data-okk=\"palette\"] .lbl{font:600 10px var(--f-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:10px;display:flex;justify-content:space-between;gap:8px}\n.okk-scope[data-okk=\"palette\"] .intro{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap;padding-block:6px 28px}\n.okk-scope[data-okk=\"palette\"] .intro h1{font-size:clamp(34px,5.4vw,70px);line-height:1;font-weight:800;margin-top:16px}\n.okk-scope[data-okk=\"palette\"] .intro h1 .c{display:inline-block;background:linear-gradient(100deg,var(--g1,#4F86FF),var(--g2,#7CC4FF) 55%,var(--g3,#E9F5FF));-webkit-background-clip:text;background-clip:text;color:transparent;transition:all .8s}\n.okk-scope[data-okk=\"palette\"] .intro p{color:var(--muted);max-width:50ch;margin:16px 0 0;font-size:clamp(15px,1.3vw,16.5px)}\n.okk-scope[data-okk=\"palette\"] .steps3{display:flex;gap:8px;flex-wrap:wrap}\n.okk-scope[data-okk=\"palette\"] .steps3 span{font:500 12px var(--f-mono);color:var(--ink);padding:7px 12px;border-radius:999px;background:rgba(255,255,255,.05)}\n.okk-scope[data-okk=\"palette\"] .steps3 b{color:var(--brand2);margin-right:6px}\n.okk-scope[data-okk=\"palette\"] .studio{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:16px;align-items:start}\n.okk-scope[data-okk=\"palette\"] .panel{padding:18px;display:flex;flex-direction:column;gap:16px}\n.okk-scope[data-okk=\"palette\"] .photo{position:relative;border-radius:18px;overflow:hidden;background:#0B0F17;aspect-ratio:4\/3;border:1.5px dashed var(--line2);transition:border-color .2s}\n.okk-scope[data-okk=\"palette\"] .photo.over{border-color:var(--brand2)}\n.okk-scope[data-okk=\"palette\"] .photo canvas{display:block;width:100%;height:100%;object-fit:contain}\n.okk-scope[data-okk=\"palette\"] .photo input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%;z-index:3}\n.okk-scope[data-okk=\"palette\"] .photo .empty[hidden]{display:none}\n.okk-scope[data-okk=\"palette\"] .photo .empty{position:absolute;inset:0;display:grid;place-items:center;text-align:center;padding:20px;pointer-events:none}\n.okk-scope[data-okk=\"palette\"] .photo .empty b{display:block;font:700 18px var(--f-display);letter-spacing:-.02em}\n.okk-scope[data-okk=\"palette\"] .photo .empty span{color:var(--muted);font-size:13.5px}\n.okk-scope[data-okk=\"palette\"] .photo .dot{position:absolute;width:26px;height:26px;margin:-13px 0 0 -13px;border-radius:50%;border:3px solid #fff;box-shadow:0 4px 14px rgba(0,0,0,.5);z-index:2;pointer-events:none;transform:scale(0);transition:transform .45s cubic-bezier(.3,1.6,.5,1)}\n.okk-scope[data-okk=\"palette\"] .photo .dot.on{transform:scale(1)}\n.okk-scope[data-okk=\"palette\"] .photo .scan{position:absolute;left:0;right:0;height:2px;top:0;background:linear-gradient(90deg,transparent,#fff,transparent);box-shadow:0 0 20px #fff;z-index:2;opacity:0}\n.okk-scope[data-okk=\"palette\"] .photo.scanning .scan{animation:scan 1s ease-in-out}\n@keyframes scan{0%{top:0;opacity:1}100%{top:100%;opacity:.2}}\n.okk-scope[data-okk=\"palette\"] .samples{display:flex;gap:8px;flex-wrap:wrap;align-items:center}\n.okk-scope[data-okk=\"palette\"] .sample{display:inline-flex;align-items:center;gap:8px;padding:6px 12px 6px 6px;border-radius:999px;border:1px solid var(--line2);font-size:13px;font-weight:500}\n.okk-scope[data-okk=\"palette\"] .sample:hover{border-color:var(--muted)}\n.okk-scope[data-okk=\"palette\"] .sample i{width:24px;height:24px;border-radius:50%;display:block}\n.okk-scope[data-okk=\"palette\"] .swatches{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px}\n.okk-scope[data-okk=\"palette\"] .sw{position:relative;border-radius:16px;overflow:hidden;border:1px solid rgba(255,255,255,.08);text-align:left;display:flex;flex-direction:column;transition:transform .2s}\n.okk-scope[data-okk=\"palette\"] .sw:hover{transform:translateY(-3px)}\n.okk-scope[data-okk=\"palette\"] .sw .c{aspect-ratio:1\/1.25;display:block;position:relative}\n.okk-scope[data-okk=\"palette\"] .sw .c em{position:absolute;right:6px;top:6px;font:600 10px var(--f-mono);font-style:normal;padding:2px 6px;border-radius:999px;background:rgba(0,0,0,.35);color:#fff}\n.okk-scope[data-okk=\"palette\"] .sw .m{padding:8px 8px 9px;background:rgba(255,255,255,.03)}\n.okk-scope[data-okk=\"palette\"] .sw .m b{display:block;font-size:12px;font-weight:600;line-height:1.2;min-height:2.4em}\n.okk-scope[data-okk=\"palette\"] .sw .m span{font:500 10.5px var(--f-mono);color:var(--muted)}\n.okk-scope[data-okk=\"palette\"] .sw.pop{animation:pop .5s cubic-bezier(.3,1.6,.5,1) both}\n@keyframes pop{from{transform:scale(.6);opacity:0}}\n.okk-scope[data-okk=\"palette\"] .rule{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px;align-items:stretch}\n.okk-scope[data-okk=\"palette\"] .bar631{display:flex;height:46px;border-radius:12px;overflow:hidden}\n.okk-scope[data-okk=\"palette\"] .bar631 div{display:grid;place-items:center;font:600 11px var(--f-mono)}\n.okk-scope[data-okk=\"palette\"] .card{border-radius:14px;padding:16px;display:flex;flex-direction:column;justify-content:space-between;gap:12px;min-height:118px;transition:background .6s,color .6s}\n.okk-scope[data-okk=\"palette\"] .card b{font:800 17px var(--f-display);letter-spacing:-.03em;line-height:1.1}\n.okk-scope[data-okk=\"palette\"] .card small{display:block;font:500 11px var(--f-mono);opacity:.8;margin-bottom:4px}\n.okk-scope[data-okk=\"palette\"] .card .pill{align-self:flex-start;padding:6px 12px;border-radius:999px;font:600 12px var(--f-body)}\n.okk-scope[data-okk=\"palette\"] .tools{display:flex;gap:8px;flex-wrap:wrap}\n.okk-scope[data-okk=\"palette\"] .poster{position:relative;border-radius:18px;overflow:hidden;background:#0B0F17;aspect-ratio:4\/5;box-shadow:0 30px 70px -30px rgba(0,0,0,.8)}\n.okk-scope[data-okk=\"palette\"] .poster canvas{display:block;width:100%;height:100%}\n.okk-scope[data-okk=\"palette\"] .tpls{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;padding:4px;border:1px solid var(--line);border-radius:14px;background:rgba(0,0,0,.2)}\n.okk-scope[data-okk=\"palette\"] .tpls button{padding:9px 4px;border-radius:10px;font-size:12.5px;font-weight:600;color:var(--muted)}\n.okk-scope[data-okk=\"palette\"] .tpls button[aria-pressed=\"true\"]{background:var(--ink);color:var(--bg)}\n.okk-scope[data-okk=\"palette\"] .txt{width:100%;height:48px;border-radius:14px;border:1px solid var(--line2);background:rgba(0,0,0,.25);color:var(--ink);font:700 17px var(--f-display);letter-spacing:-.02em;padding:0 14px}\n.okk-scope[data-okk=\"palette\"] .txt:focus{outline:none;border-color:var(--brand2)}\n.okk-scope[data-okk=\"palette\"] .row{display:flex;gap:8px;flex-wrap:wrap}\n.okk-scope[data-okk=\"palette\"] .cta{margin-top:18px;padding:clamp(22px,4vw,34px);display:grid;grid-template-columns:minmax(0,1fr) auto;gap:20px;align-items:center;background:linear-gradient(140deg,rgba(46,107,255,.16),rgba(46,107,255,.02))}\n.okk-scope[data-okk=\"palette\"] .cta h3{font-size:clamp(19px,2.4vw,26px);font-weight:700}\n.okk-scope[data-okk=\"palette\"] .cta p{margin:8px 0 0;color:var(--muted);max-width:56ch}\n.okk-scope[data-okk=\"palette\"] .shot{position:fixed;inset:0;z-index:9999;display:grid;place-items:center;background:rgba(3,4,7,.86);backdrop-filter:blur(6px);padding:16px}\n.okk-scope[data-okk=\"palette\"] .shot[hidden]{display:none!important}\n.okk-scope[data-okk=\"palette\"] .shot .in{max-width:min(560px,100%);display:flex;flex-direction:column;gap:12px;align-items:center;text-align:center}\n.okk-scope[data-okk=\"palette\"] .shot img{max-width:100%;max-height:72vh;border-radius:14px}\n.okk-scope[data-okk=\"palette\"] .shot p{margin:0;color:var(--muted);font-size:13.5px}\n.okk-scope[data-okk=\"palette\"] .toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);padding:10px 16px;border-radius:999px;background:var(--ink);color:var(--bg);font-weight:600;font-size:13px;z-index:9999;max-width:90%;text-align:center}\n@media (max-width:980px){.okk-scope[data-okk=\"palette\"] .studio{grid-template-columns:1fr}\n.okk-scope[data-okk=\"palette\"] .cta{grid-template-columns:1fr}}\n@media (max-width:560px){.okk-scope[data-okk=\"palette\"] .swatches{grid-template-columns:repeat(3,minmax(0,1fr))}\n.okk-scope[data-okk=\"palette\"] .rule{grid-template-columns:1fr}\n.okk-scope[data-okk=\"palette\"] .btn{height:44px}}\n@media (prefers-reduced-motion:reduce){.okk-scope[data-okk=\"palette\"] *{animation:none!important;transition:none!important}}\n\n\/* OK3D \u2014 fond pleine largeur, contenu centr\u00e9 et largeur ma\u00eetris\u00e9e.\n   M\u00eame cadre de 1600px et m\u00eames marges que le configurateur OK3D.\n   --okk-content-max r\u00e8gle la largeur maximale du cadre, marges comprises.\n   Le fond seul suit la largeur de l'\u00e9cran, m\u00eame sur un \u00e9cran tr\u00e8s large. *\/\n.okk-scope[data-okk=\"palette\"] {\n  --okk-content-max: 1600px;\n  --okk-gutter: clamp(22px, 5vw, 88px);\n  --okk-header-offset: 94px;\n  --okk-top-gap: 54px;\n  position: relative;\n  left: var(--okk-shift, 0px);\n  width: var(--okk-width, 100%) !important;\n  max-width: none !important;\n  min-width: 0;\n  min-height: 100vh;\n  min-height: 100dvh;\n  margin: 0 !important;\n  padding: 0 !important;\n}\n.okk-scope[data-okk=\"palette\"] .okk {\n  width: 100%;\n  overflow: clip;\n  min-height: 100vh;\n  min-height: 100dvh;\n  padding-left: 0;\n  padding-right: 0;\n  padding-top: calc(var(--okk-header-offset) + var(--okk-top-gap));\n  padding-bottom: max(28px, env(safe-area-inset-bottom, 0px));\n  scroll-margin-top: calc(var(--okk-header-offset) + var(--okk-admin-height, 0px) + 24px);\n}\n.okk-scope[data-okk=\"palette\"] .wrap {\n  width: 100%;\n  max-width: var(--okk-content-max);\n  margin: 0 auto;\n  padding-left: max(var(--okk-gutter), env(safe-area-inset-left, 0px));\n  padding-right: max(var(--okk-gutter), env(safe-area-inset-right, 0px));\n}\n.okk-scope[data-okk=\"palette\"] .intro { padding-top: 0; }\n.okk-scope[data-okk=\"palette\"] .lab { grid-template-columns: minmax(0, 1fr) clamp(360px, 28%, 400px); }\n.okk-scope[data-okk=\"palette\"] .lab > *, .okk-scope[data-okk=\"palette\"] .intro > *, .okk-scope[data-okk=\"palette\"] .studio > *,\n.okk-scope[data-okk=\"palette\"] .explain > *, .okk-scope[data-okk=\"palette\"] .rowc > *, .okk-scope[data-okk=\"palette\"] .res > *,\n.okk-scope[data-okk=\"palette\"] .cta > *, .okk-scope[data-okk=\"palette\"] .ghead > *, .okk-scope[data-okk=\"palette\"] .fb > * { min-width: 0; }\n.okk-scope[data-okk=\"palette\"] .viewcol { top: calc(var(--okk-header-offset) + var(--okk-admin-height, 0px) + 12px); }\n.okk-scope[data-okk=\"palette\"] .intro h1, .okk-scope[data-okk=\"palette\"] .studio h3,\n.okk-scope[data-okk=\"palette\"] .drop span, .okk-scope[data-okk=\"palette\"] .planfoot { overflow-wrap: anywhere; }\n.okk-scope[data-okk=\"palette\"] .rowc > .chips { flex: 1 1 0; }\n.okk-scope[data-okk=\"palette\"] input[type=color] { flex: 0 0 34px; }\n.okk-scope[data-okk=\"palette\"] .btn { max-width: 100%; height: auto; min-height: 44px; padding-block: 10px; white-space: normal; text-align: center; }\n.okk-scope[data-okk=\"palette\"] .btn.sm { min-height: 34px; padding-block: 6px; }\n.okk-scope[data-okk=\"palette\"] .explain .btn { white-space: normal; }\n.okk-scope[data-okk=\"palette\"] .bar .screen-btn { margin-left: auto; }\n.okk-scope[data-okk=\"palette\"] .game { width: 100%; max-width: 680px; margin-inline: auto; }\n.okk-scope[data-okk=\"palette\"] .icons .ico span { overflow-wrap: anywhere; }\n.okk-scope[data-okk=\"palette\"] .hint {\n  width: max-content;\n  max-width: calc(100% - 24px);\n  white-space: normal;\n  text-align: center;\n}\n.okk-scope[data-okk=\"palette\"] .fb .tx { overflow-wrap: anywhere; }\n.okk-scope[data-okk=\"palette\"] .shot { overflow-y: auto; }\n.okk-scope[data-okk=\"palette\"] .shot img, .okk-scope[data-okk=\"palette\"] .shot video { max-height: 60vh; max-height: 60dvh; object-fit: contain; }\n.okk-scope[data-okk=\"palette\"]:fullscreen {\n  left: 0 !important;\n  width: 100% !important;\n  height: 100vh;\n  height: 100dvh;\n  overflow: auto;\n  overscroll-behavior: contain;\n  --okk-admin-height: 0px !important;\n  --okk-header-offset: 0px !important;\n  --okk-top-gap: 32px !important;\n}\n.okk-scope[data-okk=\"palette\"]:-webkit-full-screen {\n  left: 0 !important;\n  width: 100% !important;\n  height: 100vh;\n  overflow: auto;\n  --okk-admin-height: 0px !important;\n  --okk-header-offset: 0px !important;\n  --okk-top-gap: 32px !important;\n}\n\/* Ces ajustements ne concernent que le widget contenant ce laboratoire. *\/\n.elementor-widget-html:has(.okk-scope[data-okk=\"palette\"]) { width: 100%; min-width: 0; padding: 0; }\n.elementor-widget-container:has(> .okk-scope[data-okk=\"palette\"]) { padding: 0; margin: 0; }\n@media (max-width: 1020px) {\n  .okk-scope[data-okk=\"palette\"] .lab { grid-template-columns: minmax(0, 1fr); }\n  .okk-scope[data-okk=\"palette\"] .viewcol { position: relative; top: 0; }\n}\n@media (max-width: 700px) {\n  .okk-scope[data-okk=\"palette\"] { --okk-header-offset: 78px; --okk-top-gap: 43px; }\n}\n@media (max-width: 560px) {\n  .okk-scope[data-okk=\"palette\"] .bar { gap: 10px; }\n  .okk-scope[data-okk=\"palette\"] .bar > a.btn { flex-basis: 100%; }\n  .okk-scope[data-okk=\"palette\"] .intro { padding-block: 0 22px; }\n  .okk-scope[data-okk=\"palette\"] .intro h1 { font-size: clamp(26px, 7.6vw, 40px); }\n  .okk-scope[data-okk=\"palette\"] .desk { padding: 12px; }\n  .okk-scope[data-okk=\"palette\"] .planfoot { flex-wrap: wrap; }\n  .okk-scope[data-okk=\"palette\"] .explain { padding: 16px; }\n  .okk-scope[data-okk=\"palette\"] .stats { flex-wrap: wrap; }\n  .okk-scope[data-okk=\"palette\"] .hud.tr { right: 12px; }\n  .okk-scope[data-okk=\"palette\"] .hud.tl { left: 14px; top: auto; bottom: 54px; }\n}\n\n\/* Affichage app lorsque le plein \u00e9cran natif n'est pas disponible. *\/\n.okk-scope[data-okk=\"palette\"].is-app-screen {\n  position: fixed !important; inset: 0 !important; left: 0 !important;\n  width: 100% !important; max-width: none !important; margin: 0 !important;\n  height: 100vh; height: 100dvh; overflow: auto; overscroll-behavior: contain;\n  z-index: 2147483646;\n}\n.okk-scope[data-okk=\"palette\"]:fullscreen, .okk-scope[data-okk=\"palette\"].is-app-screen {\n  --okk-header-offset: 0px !important; --okk-top-gap: 32px !important;\n  --okk-admin-height: 0px !important;\n  left: 0 !important; width: 100% !important; max-width: none !important; margin: 0 !important;\n  height: 100vh; height: 100dvh; overflow: auto; overscroll-behavior: contain;\n  \n}\n.okk-scope[data-okk=\"palette\"]:-webkit-full-screen {\n  --okk-header-offset: 0px !important; --okk-top-gap: 32px !important; --okk-admin-height: 0px !important;\n  left: 0 !important; width: 100% !important; max-width: none !important; margin: 0 !important;\n  height: 100vh; overflow: auto;\n  \n}\n.okk-scope[data-okk=\"palette\"] .appbar {\n  display: flex; justify-content: flex-end; align-items: center; gap: 12px;\n  flex-wrap: wrap; padding: 0 0 18px;\n}\n.okk-scope[data-okk=\"palette\"] .screen-btn { margin-left: auto; flex: 0 0 auto; }\n.okk-scope[data-okk=\"palette\"]:fullscreen .appbar, .okk-scope[data-okk=\"palette\"].is-app-screen .appbar,\n.okk-scope[data-okk=\"palette\"]:fullscreen .bar, .okk-scope[data-okk=\"palette\"].is-app-screen .bar {\n  position: sticky; top: 0; z-index: 40; background: var(--bg, #06080c);\n  padding-block: 10px; margin-bottom: 12px;\n}\n\n\n\/* Garder lisibles les libell\u00e9s de couleur et les commandes sur petit \u00e9cran. *\/\n.okk-scope[data-okk=\"palette\"] .studio > *, .okk-scope[data-okk=\"palette\"] .rule > * { min-width: 0; }\n.okk-scope[data-okk=\"palette\"] .lbl { flex-wrap: wrap; }\n.okk-scope[data-okk=\"palette\"] .sw .m b, .okk-scope[data-okk=\"palette\"] .sw .m span { overflow-wrap: anywhere; }\n.okk-scope[data-okk=\"palette\"] .tpls button { overflow-wrap: anywhere; }\n.okk-scope[data-okk=\"palette\"] .sample { max-width: 100%; text-align: left; }\n\n<\/style>\n<div class=\"okk-scope\" data-okk=\"palette\">\n<div class=\"okk\" id=\"okk\">\n  <div class=\"wrap\">\n    <div class=\"appbar\"><button type=\"button\" class=\"btn gho sm screen-btn\" data-okk-fullscreen aria-pressed=\"false\">\u26f6 Plein \u00e9cran<\/button><\/div>\n    <section class=\"intro\">\n      <div><span class=\"eyebrow\">OK3D Lab \u00b7 Couleur<\/span>\n        <h1>Ta photo.<br><span class=\"c\">Ta palette.<\/span> Ton affiche.<\/h1>\n        <p>D\u00e9pose une photo : on en extrait les couleurs qui comptent, on leur donne un nom, et on compose une affiche avec. Tout se passe dans ton navigateur.<\/p><\/div>\n      <div class=\"steps3\"><span><b>01<\/b>Une photo<\/span><span><b>02<\/b>6 couleurs<\/span><span><b>03<\/b>Une affiche<\/span><\/div>\n    <\/section>\n\n    <section class=\"studio\">\n      <div class=\"glass panel\">\n        <div class=\"photo\" id=\"photo\"><canvas id=\"pc\" width=\"640\" height=\"480\"><\/canvas><div class=\"scan\"><\/div><div id=\"dots\"><\/div>\n          <div class=\"empty\" id=\"empty\"><div><b>D\u00e9pose ta photo ici<\/b><span>ou touche pour en choisir une \u00b7 JPG, PNG, HEIC converti<\/span><\/div><\/div>\n          <input type=\"file\" id=\"file\" accept=\"image\/*\" aria-label=\"Choisir une photo\"><\/div>\n        <div class=\"samples\" id=\"samples\"><span class=\"lbl\" style=\"margin:0\">Ou essaie<\/span><\/div>\n        <div><div class=\"lbl\"><span>Ta palette<\/span><span id=\"hint\">Touche une couleur pour copier son code<\/span><\/div><div class=\"swatches\" id=\"sw\"><\/div><\/div>\n        <div class=\"rule\">\n          <div><div class=\"lbl\">R\u00e8gle 60 \u00b7 30 \u00b7 10<\/div><div class=\"bar631\" id=\"bar\"><\/div><p style=\"margin:10px 0 0;color:var(--muted);font-size:13px\">60 % de couleur dominante, 30 % de secondaire, 10 % d'accent : la recette d'un ensemble \u00e9quilibr\u00e9.<\/p><\/div>\n          <div class=\"card\" id=\"card\"><div><small>Aper\u00e7u<\/small><b id=\"cardt\">Ma palette<\/b><\/div><span class=\"pill\" id=\"pill\">D\u00e9couvrir<\/span><\/div>\n        <\/div>\n        <div class=\"tools\"><button type=\"button\" class=\"btn gho sm\" id=\"css\">Copier en CSS<\/button><button type=\"button\" class=\"btn gho sm\" id=\"pcard\">Exporter la palette<\/button><\/div>\n      <\/div>\n\n      <div class=\"glass panel\">\n        <div class=\"tpls\" id=\"tpls\"><\/div>\n        <div class=\"poster\"><canvas id=\"poster\" width=\"1080\" height=\"1350\"><\/canvas><\/div>\n        <div><div class=\"lbl\">Titre de l'affiche<\/div><input class=\"txt\" id=\"title\" maxlength=\"40\" autocomplete=\"off\"><\/div>\n        <div class=\"row\"><button type=\"button\" class=\"btn gho\" id=\"remix\">\u21bb Remixer<\/button><button type=\"button\" class=\"btn pri\" id=\"export\">Exporter l'affiche<\/button><\/div>\n      <\/div>\n    <\/section>\n\n    <section class=\"glass cta\"><div><span class=\"eyebrow\">Studio OK3D<\/span><h3 style=\"margin-top:12px\">Une palette, c'est le d\u00e9but d'une identit\u00e9.<\/h3>\n      <p>OK3D cr\u00e9e des identit\u00e9s visuelles, des visuels 3D et des animations qui donnent vie \u00e0 vos couleurs. Un projet en t\u00eate ?<\/p><\/div>\n      <a class=\"btn pri\" id=\"ctalink\" href=\"#\" target=\"_blank\" rel=\"noopener\">Parlons-en \u2192<\/a><\/section>\n  <\/div>\n  <div class=\"shot\" id=\"shot\" hidden><div class=\"in\"><img id=\"shotimg\" alt=\"Export\"><p id=\"shotp\"><\/p><div class=\"row\" style=\"justify-content:center\"><a class=\"btn pri sm\" id=\"shotdl\" hidden>T\u00e9l\u00e9charger<\/a><button type=\"button\" class=\"btn gho sm\" id=\"shotx\">Fermer<\/button><\/div><\/div><\/div>\n<\/div>\n<\/div>\n\n<script>\n(function(){\nconst OKK=document.querySelector('[data-okk=\"palette\"]:not([data-okk-ready])');\nif(!OKK) return;\nOKK.setAttribute('data-okk-ready', 'true');\n\n\n\/* =====================================================================\n   CONFIG\n   ===================================================================== *\/\nconst CONFIG={ brand:\"OK3D\", contactUrl:\"https:\/\/www.ok3d.fr\/#contact\", allowDownload:true, colors:6 };\n\nconst $=s=>OKK.querySelector(s);\nconst clamp=(v,a,b)=>Math.max(a,Math.min(b,v));\nconst hex=c=>\"#\"+c.map(v=>Math.round(clamp(v,0,255)).toString(16).padStart(2,\"0\")).join(\"\").toUpperCase();\nconst h2r=h=>[1,3,5].map(i=>parseInt(h.slice(i,i+2),16));\nconst lum=c=>{ const f=v=>{ v\/=255; return v<=.03928?v\/12.92:Math.pow((v+.055)\/1.055,2.4) }; return .2126*f(c[0])+.7152*f(c[1])+.0722*f(c[2]) };\nconst contrast=(a,b)=>{ const A=lum(a),B=lum(b); return (Math.max(A,B)+.05)\/(Math.min(A,B)+.05) };\nconst sat=c=>{ const mx=Math.max(...c), mn=Math.min(...c); return mx===0?0:(mx-mn)\/mx };\nfunction toast(t){ const d=document.createElement(\"div\"); d.className=\"toast\"; d.textContent=t; $(\"#okk\").appendChild(d); setTimeout(()=>d.remove(),2400) }\nfunction rng(seed){ return ()=>{ seed|=0; seed=seed+0x6D2B79F5|0; let t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)\/4294967296 } }\n\n\/* ---------- names ---------- *\/\nconst NAMES=[[\"Noir d'encre\",\"#14161A\"],[\"Charbon\",\"#2E3236\"],[\"Ardoise\",\"#4A5560\"],[\"Gris b\u00e9ton\",\"#8D9196\"],[\"Gris perle\",\"#B8BEC6\"],[\"Neige\",\"#F4F5F2\"],[\"Cr\u00e8me\",\"#F1E6CF\"],[\"Lin\",\"#E6DCC8\"],\n [\"Sable\",\"#D9C29C\"],[\"Camel\",\"#B98A57\"],[\"Ocre\",\"#C98B2B\"],[\"Terre cuite\",\"#C0603A\"],[\"Brique\",\"#9C3B2A\"],[\"Corail\",\"#F07A5E\"],[\"P\u00eache\",\"#F6B38E\"],[\"Abricot\",\"#F2A65A\"],[\"Orange\",\"#EF7E2A\"],\n [\"Rose poudr\u00e9\",\"#E8B4B8\"],[\"Rose bonbon\",\"#F28DB2\"],[\"Framboise\",\"#C2185B\"],[\"Rouge\",\"#D33A2F\"],[\"Bordeaux\",\"#6E1F2C\"],[\"Prune\",\"#5E2A4E\"],[\"Lavande\",\"#A99BD6\"],[\"Violet\",\"#6A4BC4\"],\n [\"Bleu nuit\",\"#16224A\"],[\"Bleu marine\",\"#1F3466\"],[\"Bleu roi\",\"#2848B8\"],[\"Bleu acier\",\"#5B7A99\"],[\"Bleu ciel\",\"#8EC5EA\"],[\"Glace\",\"#DDEEF6\"],[\"Lagon\",\"#1FA3A8\"],[\"Turquoise\",\"#3CCFC0\"],\n [\"Menthe\",\"#9EDFC1\"],[\"Sauge\",\"#A3B59A\"],[\"Olive\",\"#7A7B3A\"],[\"Vert for\u00eat\",\"#25553A\"],[\"Vert sapin\",\"#163A2C\"],[\"Vert tendre\",\"#8CC163\"],[\"Moutarde\",\"#D4A62A\"],[\"Jaune soleil\",\"#F5CF3A\"],\n [\"Citron\",\"#F2E86D\"],[\"Chocolat\",\"#4B2E22\"],[\"Caf\u00e9\",\"#6F4A35\"],[\"Taupe\",\"#8B7B6E\"]].map(([n,h])=>[n,h2r(h)]);\nfunction nameOf(c){ let best=null,bd=1e9; for(const [n,r] of NAMES){ const rm=(c[0]+r[0])\/2, d=(2+rm\/256)*(c[0]-r[0])**2+4*(c[1]-r[1])**2+(2+(255-rm)\/256)*(c[2]-r[2])**2; if(d<bd){ bd=d; best=n } } return best }\n\n\/* ---------- sample \"photos\" (drawn) ---------- *\/\nfunction grain(x,w,h,a=14){ const im=x.getImageData(0,0,w,h), d=im.data; for(let i=0;i<d.length;i+=4){ const n=(Math.random()-.5)*a; d[i]+=n; d[i+1]+=n; d[i+2]+=n } x.putImageData(im,0,0) }\nconst SAMPLES=[\n {name:\"Coucher de soleil\",title:\"Golden hour\",dot:\"linear-gradient(135deg,#F6B37A,#E8607A,#24345E)\",draw(x,w,h){ let g=x.createLinearGradient(0,0,0,h*.62); g.addColorStop(0,\"#1B1F4A\"); g.addColorStop(.55,\"#E8607A\"); g.addColorStop(1,\"#F6B37A\"); x.fillStyle=g; x.fillRect(0,0,w,h);\n   x.fillStyle=\"#FFD38A\"; x.beginPath(); x.arc(w*.62,h*.58,58,0,7); x.fill(); g=x.createLinearGradient(0,h*.6,0,h); g.addColorStop(0,\"#3B3F6E\"); g.addColorStop(1,\"#141A33\"); x.fillStyle=g; x.fillRect(0,h*.6,w,h*.4);\n   x.fillStyle=\"rgba(255,200,140,.55)\"; for(let i=0;i<14;i++){ const y=h*.62+i*11; x.fillRect(w*.62-60+Math.sin(i)*20,y,120-i*6,3) } x.fillStyle=\"#0E1226\"; x.beginPath(); x.moveTo(0,h*.66); x.lineTo(w*.3,h*.5); x.lineTo(w*.45,h*.62); x.lineTo(0,h*.7); x.fill() }},\n {name:\"For\u00eat brumeuse\",title:\"Sous-bois\",dot:\"linear-gradient(135deg,#C9D6C4,#5E7F62,#2F4B38)\",draw(x,w,h){ const g=x.createLinearGradient(0,0,0,h); g.addColorStop(0,\"#D7E0CF\"); g.addColorStop(.6,\"#8FA88C\"); g.addColorStop(1,\"#3B3326\"); x.fillStyle=g; x.fillRect(0,0,w,h);\n   const cols=[\"#7A9A7E\",\"#5E7F62\",\"#3F6047\",\"#2F4B38\",\"#1F3427\"]; cols.forEach((c,k)=>{ x.fillStyle=c; for(let i=0;i<9;i++){ const cx=(i+.5)*w\/8+((k*37)%40)-20, base=h*(.55+k*.1), hh=120+k*30; x.beginPath(); x.moveTo(cx,base-hh); x.lineTo(cx+40+k*6,base); x.lineTo(cx-40-k*6,base); x.fill() } });\n   x.fillStyle=\"#4A3A28\"; x.fillRect(0,h*.9,w,h*.1); x.fillStyle=\"rgba(240,226,190,.5)\"; x.beginPath(); x.ellipse(w*.75,h*.2,90,40,0,0,7); x.fill() }},\n {name:\"Riviera\",title:\"\u00c9t\u00e9 \u00e0 Nice\",dot:\"linear-gradient(135deg,#9ED4F2,#1F7FB6,#D9733E)\",draw(x,w,h){ x.fillStyle=\"#9ED4F2\"; x.fillRect(0,0,w,h*.45); const g=x.createLinearGradient(0,h*.45,0,h); g.addColorStop(0,\"#2E95C7\"); g.addColorStop(1,\"#14527D\"); x.fillStyle=g; x.fillRect(0,h*.45,w,h*.55);\n   const H=[[\"#F2E6D0\",40,150,90,120],[\"#EBD3B0\",130,170,80,100],[\"#F5EEE3\",205,140,95,130],[\"#E9C9A0\",300,180,70,90]]; H.forEach(([c,X,Y,W,HH])=>{ x.fillStyle=c; x.fillRect(X,Y,W,HH); x.fillStyle=\"#D9733E\"; x.beginPath(); x.moveTo(X-6,Y); x.lineTo(X+W\/2,Y-30); x.lineTo(X+W+6,Y); x.fill(); x.fillStyle=\"#2B5F7E\"; for(let r=0;r<2;r++) for(let q=0;q<2;q++) x.fillRect(X+14+q*W\/2,Y+20+r*40,14,20) });\n   x.fillStyle=\"#E8D7B7\"; x.beginPath(); x.moveTo(0,h*.6); x.quadraticCurveTo(w*.3,h*.5,w*.55,h*.62); x.lineTo(w*.55,h); x.lineTo(0,h); x.fill(); x.fillStyle=\"#FFFFFF\"; x.fillRect(w*.7,h*.6,30,6) }},\n];\n\n\/* ---------- extraction (k-means) ---------- *\/\nconst pc=$(\"#pc\"), px=pc.getContext(\"2d\",{willReadFrequently:true});\nlet PAL=[], title=\"\", seed=1, tpl=\"bauhaus\", posterTitle=\"\";\nfunction extract(){\n  const W=pc.width,H=pc.height, s=Math.max(1,Math.round(Math.sqrt(W*H\/9000))); const d=px.getImageData(0,0,W,H).data; const P=[];\n  for(let y=0;y<H;y+=s) for(let x=0;x<W;x+=s){ const i=(y*W+x)*4; if(d[i+3]<128) continue; P.push([d[i],d[i+1],d[i+2],x,y]) }\n  const K=Math.min(CONFIG.colors+4,P.length), C=[P[Math.random()*P.length|0].slice(0,3)];\n  while(C.length<K){ let sum=0; const D=P.map(p=>{ let m=1e9; for(const c of C){ const v=(p[0]-c[0])**2+(p[1]-c[1])**2+(p[2]-c[2])**2; if(v<m) m=v } sum+=m; return m }); let r=Math.random()*sum, i=0; while(r>D[i]&&i<D.length-1){ r-=D[i]; i++ } C.push(P[i].slice(0,3)) }\n  let A=new Array(P.length).fill(0);\n  for(let it=0;it<12;it++){ const S=C.map(()=>[0,0,0,0]); P.forEach((p,j)=>{ let b=0,bm=1e9; C.forEach((c,k)=>{ const v=(p[0]-c[0])**2+(p[1]-c[1])**2+(p[2]-c[2])**2; if(v<bm){ bm=v; b=k } }); A[j]=b; S[b][0]+=p[0]; S[b][1]+=p[1]; S[b][2]+=p[2]; S[b][3]++ });\n    C.forEach((c,k)=>{ if(S[k][3]) C[k]=[S[k][0]\/S[k][3],S[k][1]\/S[k][3],S[k][2]\/S[k][3]] }) }\n  let cl=C.map((c,k)=>({c,n:0,pos:null,bd:1e9,sx:0,sy:0,m:0}));\n  P.forEach((p,j)=>{ const o=cl[A[j]]; o.n++; const v=(p[0]-o.c[0])**2+(p[1]-o.c[1])**2+(p[2]-o.c[2])**2; if(v<900){ o.sx+=p[3]; o.sy+=p[4]; o.m++ } if(v<o.bd){ o.bd=v; o.pos=[p[3],p[4]] } });\n  cl.forEach(o=>{ if(!o.m) return; const cx=o.sx\/o.m, cy=o.sy\/o.m; let best=1e12; o.bd2=1e9; });\n  P.forEach((p,j)=>{ const o=cl[A[j]]; if(!o.m) return; const v=(p[0]-o.c[0])**2+(p[1]-o.c[1])**2+(p[2]-o.c[2])**2; if(v>=900) return; const dsp=(p[3]-o.sx\/o.m)**2+(p[4]-o.sy\/o.m)**2; if(dsp<o.bd2){ o.bd2=dsp; o.pos=[p[3],p[4]] } });\n  cl=cl.filter(o=>o.n>0).sort((a,b)=>b.n-a.n);\n  const out=[]; for(const o of cl){ if(out.some(q=>Math.hypot(q.c[0]-o.c[0],q.c[1]-o.c[1],q.c[2]-o.c[2])<26)){ const q=out.find(q=>Math.hypot(q.c[0]-o.c[0],q.c[1]-o.c[1],q.c[2]-o.c[2])<26); q.n+=o.n; continue } out.push(o) }\n  const tot=out.reduce((a,o)=>a+o.n,0); const dd=(a,b)=>Math.hypot(a.c[0]-b.c[0],a.c[1]-b.c[1],a.c[2]-b.c[2]);\n  const sel=[out[0]]; while(sel.length<Math.min(CONFIG.colors,out.length)){ let best=null,bs=-1; for(const o of out){ if(sel.includes(o)) continue; const v=Math.sqrt(o.n\/tot)*Math.min(...sel.map(q=>dd(o,q))); if(v>bs){ bs=v; best=o } } sel.push(best) }\n  sel.sort((a,b)=>b.n-a.n); const used={};\n  PAL=sel.map(o=>{ let n=nameOf(o.c); if(used[n]){ n+=lum(o.c)>lum(used[n])?\" clair\":\" profond\" } else used[n]=o.c; return {rgb:o.c.map(Math.round),hex:hex(o.c),name:n,share:o.n\/tot,pos:o.pos} });\n  const st=PAL.reduce((a,p)=>a+p.share,0); PAL.forEach(p=>p.share\/=st);\n}\nfunction loadImage(img,name,ttl){\n  const r=Math.min(1,960\/Math.max(img.width,img.height)); pc.width=Math.round(img.width*r); pc.height=Math.round(img.height*r); $(\"#photo\").style.aspectRatio=`${clamp(pc.width\/pc.height,.75,1.78)}`; px.drawImage(img,0,0,pc.width,pc.height); analyse(name,ttl) }\nfunction analyse(name,ttl){\n  $(\"#empty\").hidden=true; const ph=$(\"#photo\"); ph.classList.remove(\"scanning\"); void ph.offsetWidth; ph.classList.add(\"scanning\");\n  extract(); title=ttl||name||\"Ma palette\"; $(\"#title\").value=title; seed=Math.random()*1e9|0; renderPalette(); render() }\nfunction renderPalette(){\n  $(\"#sw\").innerHTML=PAL.map((p,i)=>`<button type=\"button\" class=\"sw pop\" style=\"animation-delay:${i*70}ms\" data-i=\"${i}\" title=\"Copier ${p.hex}\"><span class=\"c\" style=\"background:${p.hex}\"><em>${Math.round(p.share*100)} %<\/em><\/span><span class=\"m\"><b>${p.name}<\/b><span>${p.hex}<\/span><\/span><\/button>`).join(\"\");\n  OKK.querySelectorAll(\".sw\").forEach(b=>b.onclick=()=>{ const p=PAL[+b.dataset.i]; copy(p.hex,`${p.name} \u00b7 ${p.hex} copi\u00e9`) });\n  \/\/ dots on photo\n  const box=$(\"#photo\").getBoundingClientRect(), k=Math.min(box.width\/pc.width,box.height\/pc.height), ox=(box.width-pc.width*k)\/2, oy=(box.height-pc.height*k)\/2;\n  $(\"#dots\").innerHTML=PAL.map((p,i)=>`<span class=\"dot\" style=\"left:${(ox+p.pos[0]*k)\/box.width*100}%;top:${(oy+p.pos[1]*k)\/box.height*100}%;background:${p.hex};transition-delay:${300+i*90}ms\"><\/span>`).join(\"\");\n  requestAnimationFrame(()=>requestAnimationFrame(()=>OKK.querySelectorAll(\"#dots .dot\").forEach(d=>d.classList.add(\"on\"))));\n  \/\/ 60-30-10\n  const R=roles(); $(\"#bar\").innerHTML=[[R.a,60],[R.b,30],[R.c,10]].map(([p,w])=>`<div style=\"flex:${w};background:${p.hex};color:${textOn(p.rgb)}\">${w} %<\/div>`).join(\"\");\n  const card=$(\"#card\"); card.style.background=R.a.hex; card.style.color=textOn(R.a.rgb,R.b.rgb); $(\"#cardt\").textContent=title; const pill=$(\"#pill\"); pill.style.background=R.c.hex; pill.style.color=textOn(R.c.rgb);\n  \/\/ ambience of the page\n  const by=[...PAL].sort((a,b)=>sat(b.rgb)-sat(a.rgb)); const okk=$(\"#okk\");\n  okk.style.setProperty(\"--amb\",`rgba(${by[0].rgb.join(\",\")},.28)`); okk.style.setProperty(\"--amb2\",`rgba(${(by[1]||by[0]).rgb.join(\",\")},.14)`);\n  const lg=[...PAL].sort((a,b)=>lum(b.rgb)-lum(a.rgb)).filter(p=>lum(p.rgb)>.08); const h1=OKK.querySelector(\".intro h1 .c\");\n  h1.style.setProperty(\"--g1\",(by[0]||PAL[0]).hex); h1.style.setProperty(\"--g2\",(by[1]||PAL[1]||PAL[0]).hex); h1.style.setProperty(\"--g3\",(lg[0]||PAL[0]).hex);\n}\nfunction roles(){ const s=[...PAL]; const a=s[0], rest=s.slice(1); const c=[...rest].sort((x,y)=>(sat(y.rgb)*contrast(y.rgb,a.rgb))-(sat(x.rgb)*contrast(x.rgb,a.rgb)))[0]||a; const b=rest.find(p=>p!==c)||a; return {a,b,c} }\nfunction textOn(bg,pref){ if(pref&&contrast(bg,pref)>=4.5) return hex(pref); return contrast(bg,[255,255,255])>=contrast(bg,[17,19,24])?\"#FFFFFF\":\"#111318\" }\n\n\/* ---------- posters ---------- *\/\nconst PC=$(\"#poster\"), X=PC.getContext(\"2d\"); const TPL=[[\"bauhaus\",\"Bauhaus\"],[\"suisse\",\"Suisse\"],[\"degrade\",\"D\u00e9grad\u00e9\"],[\"arcs\",\"Arcs\"]];\nfunction wrapText(t,maxW,font,maxLines=4){ X.font=font; const words=t.split(\/\\s+\/).filter(Boolean); const lines=[]; let cur=\"\";\n  for(const w of words){ const tryL=cur?cur+\" \"+w:w; if(X.measureText(tryL).width<=maxW||!cur) cur=tryL; else { lines.push(cur); cur=w } } if(cur) lines.push(cur); return lines.slice(0,maxLines) }\nfunction fitTitle(t,maxW,maxH,weight=\"800\",fam=\"Unbounded\",start=170){ let size=start; let lines; do{ lines=wrapText(t,maxW,`${weight} ${size}px ${fam}, 'Arial Black', sans-serif`); if(lines.length*size*1.02<=maxH&&lines.every(l=>X.measureText(l).width<=maxW)) break; size-=6 }while(size>40); return {lines,size} }\nfunction sortL(){ return [...PAL].sort((a,b)=>lum(a.rgb)-lum(b.rgb)) }\nfunction footerText(ink,y=1290){ X.fillStyle=ink; X.textAlign=\"left\"; X.font=\"600 22px 'Geist Mono', ui-monospace, monospace\"; X.fillText(PAL.map(p=>p.hex).join(\"   \"),70,y-36); X.globalAlpha=.7; X.fillText(`PALETTE STUDIO \u00b7 ${CONFIG.brand}`,70,y); X.globalAlpha=1 }\nfunction render(){\n  if(!PAL.length) return; const W=1080,H=1350, R=rng(seed), L=sortL(), t=($(\"#title\").value||title||\"Ma palette\").trim(); X.save(); X.clearRect(0,0,W,H);\n  const pick=()=>PAL[R()*PAL.length|0];\n  if(tpl===\"bauhaus\"){ const bg=L[L.length-1]; X.fillStyle=bg.hex; X.fillRect(0,0,W,H); const cols=PAL.filter(p=>p!==bg); const cs=270, ox=0, oy=0;\n    for(let r=0;r<3;r++) for(let c=0;c<4;c++){ const x=c*cs, y=r*cs; const col=cols[(r*4+c+(R()*3|0))%cols.length].hex; const k=R()*6|0; X.fillStyle=col;\n      if(k===0){ X.beginPath(); X.arc(x+cs\/2,y+cs\/2,cs\/2-14,0,7); X.fill() } else if(k===1){ X.beginPath(); const a=(R()*4|0)*Math.PI\/2; X.arc(x+cs\/2,y+cs\/2,cs\/2-10,a,a+Math.PI); X.fill() }\n      else if(k===2){ const q=R()*4|0, cx=x+(q&1?cs:0), cy=y+(q&2?cs:0); X.beginPath(); X.moveTo(cx,cy); X.arc(cx,cy,cs-12,[0,Math.PI\/2,Math.PI*1.5,Math.PI][q]-(q===1?0:0),[0,Math.PI\/2,Math.PI*1.5,Math.PI][q]+Math.PI\/2); X.fill() }\n      else if(k===3){ X.fillRect(x+16,y+16,cs-32,cs-32) } else if(k===4){ X.beginPath(); X.moveTo(x+14,y+cs-14); X.lineTo(x+cs\/2,y+14); X.lineTo(x+cs-14,y+cs-14); X.fill() } else { X.fillRect(x+16,y+cs\/2-20,cs-32,40); X.fillRect(x+cs\/2-20,y+16,40,cs-32) } }\n    const ink=textOn(bg.rgb); const ft=fitTitle(t.toUpperCase(),940,330,\"800\",\"Unbounded\",150); X.fillStyle=ink; X.font=`800 ${ft.size}px Unbounded, 'Arial Black', sans-serif`; ft.lines.forEach((l,i)=>X.fillText(l,70,890+ft.size*(i+.85)));\n    footerText(ink) }\n  else if(tpl===\"suisse\"){ const bg=L[0]; X.fillStyle=bg.hex; X.fillRect(0,0,W,H); const ink=textOn(bg.rgb,L[L.length-1].rgb); X.strokeStyle=ink; X.globalAlpha=.25; X.lineWidth=2; for(let i=1;i<6;i++){ X.beginPath(); X.moveTo(i*W\/6,0); X.lineTo(i*W\/6,H); X.stroke() } X.globalAlpha=1;\n    let x=70; const bw=940, tw=PAL.reduce((a,p)=>a+Math.max(30,p.share*bw),0); PAL.forEach(p=>{ const w=Math.max(30,p.share*bw)*bw\/tw; X.fillStyle=p.hex; X.fillRect(x,820,w,330); x+=w }); X.strokeStyle=ink; X.lineWidth=2; X.strokeRect(70,820,bw,330);\n    X.fillStyle=ink; X.font=\"600 26px 'Geist Mono', monospace\"; X.fillText(\"N\u00b0 \"+String(seed%97+1).padStart(2,\"0\"),70,110); X.textAlign=\"right\"; X.fillText(new Date().getFullYear(),1010,110); X.textAlign=\"left\";\n    const ft=fitTitle(t,940,560,\"800\",\"Geist\",190); X.font=`800 ${ft.size}px Geist, Arial, sans-serif`; X.fillStyle=ink; ft.lines.forEach((l,i)=>X.fillText(l,62,200+ft.size*(i+.82))); footerText(ink,1230); }\n  else if(tpl===\"degrade\"){ const bg=L[0]; X.fillStyle=bg.hex; X.fillRect(0,0,W,H); X.filter=\"blur(90px)\";\n    PAL.forEach((p,i)=>{ X.fillStyle=p.hex; X.globalAlpha=.9; X.beginPath(); X.ellipse(R()*W,R()*H,220+R()*260,200+R()*260,R()*3,0,7); X.fill() }); X.filter=\"none\"; X.globalAlpha=1;\n    const g=X.getImageData(0,0,W,H), d=g.data; for(let i=0;i<d.length;i+=4){ const n=(Math.random()-.5)*22; d[i]+=n; d[i+1]+=n; d[i+2]+=n } X.putImageData(g,0,0);\n    const ink=\"#FFFFFF\"; X.fillStyle=ink; X.textAlign=\"center\"; const ft=fitTitle(t,900,520,\"800\",\"Unbounded\",160); X.font=`800 ${ft.size}px Unbounded, 'Arial Black', sans-serif`; X.shadowColor=\"rgba(0,0,0,.25)\"; X.shadowBlur=30;\n    const top=H\/2-ft.lines.length*ft.size*1.02\/2; ft.lines.forEach((l,i)=>X.fillText(l,W\/2,top+ft.size*(i+.82))); X.shadowBlur=0; X.textAlign=\"left\"; footerText(ink) }\n  else { const bg=L[L.length-1]; X.fillStyle=bg.hex; X.fillRect(0,0,W,H); const cols=PAL.filter(p=>p!==bg); const corner=R()*4|0; const cx=corner&1?W:0, cy=corner&2?H*.7:0; const th=78;\n    for(let i=0;i<9;i++){ X.strokeStyle=cols[i%cols.length].hex; X.lineWidth=th-4; X.beginPath(); X.arc(cx,cy,140+i*th,0,7); X.stroke() }\n    const ink=textOn(bg.rgb); X.fillStyle=bg.hex; X.fillRect(0,H*.72,W,H*.28); X.fillStyle=ink; const ft=fitTitle(t,940,250,\"800\",\"Unbounded\",130); X.font=`800 ${ft.size}px Unbounded, 'Arial Black', sans-serif`; ft.lines.forEach((l,i)=>X.fillText(l,70,H*.72+40+ft.size*(i+.82))); footerText(ink) }\n  X.restore();\n}\n\n\/* ---------- export ---------- *\/\nfunction openShot(url,name){ $(\"#shotimg\").src=url; const dl=$(\"#shotdl\"); dl.hidden=!CONFIG.allowDownload; if(CONFIG.allowDownload){ dl.href=url; dl.download=name }\n  $(\"#shotp\").textContent=CONFIG.allowDownload?\"Pr\u00eat \u00e0 partager.\":\"Clic droit ou appui long sur l'image pour l'enregistrer.\"; $(\"#shot\").hidden=false }\nfunction exportCard(){ const c=document.createElement(\"canvas\"); c.width=1080; c.height=1080; const x=c.getContext(\"2d\"); x.fillStyle=\"#0B0D12\"; x.fillRect(0,0,1080,1080);\n  const n=PAL.length, w=(1080-120-(n-1)*16)\/n; PAL.forEach((p,i)=>{ const X0=60+i*(w+16); x.fillStyle=p.hex; x.beginPath(); x.roundRect?x.roundRect(X0,200,w,600,28):x.rect(X0,200,w,600); x.fill();\n    x.fillStyle=\"#EEF2F8\"; x.font=\"700 26px Geist, Arial, sans-serif\"; const words=p.name.split(\" \"); words.forEach((wd,k)=>x.fillText(wd,X0,850+k*30)); x.fillStyle=\"#8792A6\"; x.font=\"600 22px 'Geist Mono'\"; x.fillText(p.hex,X0,850+words.length*30+8) });\n  x.fillStyle=\"#EEF2F8\"; x.font=\"800 58px Unbounded, 'Arial Black', sans-serif\"; x.fillText(($(\"#title\").value||title).slice(0,24),60,130); x.fillStyle=\"#8792A6\"; x.font=\"600 22px 'Geist Mono'\"; x.fillText(`PALETTE STUDIO \u00b7 ${CONFIG.brand}`,60,1030);\n  openShot(c.toDataURL(\"image\/png\"),\"palette.png\") }\nfunction copy(t,msg){ (navigator.clipboard?navigator.clipboard.writeText(t):Promise.reject()).then(()=>toast(msg||\"Copi\u00e9\")).catch(()=>toast(\"Copie impossible ici : \"+t)) }\n\n\/* ---------- UI ---------- *\/\nfunction ui(){\n  $(\"#samples\").insertAdjacentHTML(\"beforeend\",SAMPLES.map((s,i)=>`<button type=\"button\" class=\"sample\" data-s=\"${i}\"><i style=\"background:${s.dot}\"><\/i>${s.name}<\/button>`).join(\"\"));\n  OKK.querySelectorAll(\".sample\").forEach(b=>b.onclick=()=>useSample(+b.dataset.s));\n  $(\"#tpls\").innerHTML=TPL.map(([k,l])=>`<button type=\"button\" data-t=\"${k}\" aria-pressed=\"${k===tpl}\">${l}<\/button>`).join(\"\");\n  OKK.querySelectorAll(\"#tpls button\").forEach(b=>b.onclick=()=>{ tpl=b.dataset.t; OKK.querySelectorAll(\"#tpls button\").forEach(x=>x.setAttribute(\"aria-pressed\",x===b)); render() });\n  $(\"#file\").onchange=e=>{ const f=e.target.files[0]; if(f) readFile(f); e.target.value=\"\" };\n  const ph=$(\"#photo\"); [\"dragenter\",\"dragover\"].forEach(t=>ph.addEventListener(t,e=>{ e.preventDefault(); ph.classList.add(\"over\") })); [\"dragleave\",\"drop\"].forEach(t=>ph.addEventListener(t,()=>ph.classList.remove(\"over\")));\n  ph.addEventListener(\"drop\",e=>{ e.preventDefault(); const f=e.dataTransfer.files[0]; if(f) readFile(f) });\n  $(\"#title\").oninput=()=>{ render(); $(\"#cardt\").textContent=$(\"#title\").value||title };\n  $(\"#remix\").onclick=()=>{ seed=Math.random()*1e9|0; render() };\n  $(\"#export\").onclick=()=>{ render(); openShot(PC.toDataURL(\"image\/png\"),\"affiche-palette.png\") };\n  $(\"#pcard\").onclick=exportCard; $(\"#shotx\").onclick=()=>$(\"#shot\").hidden=true;\n  $(\"#css\").onclick=()=>copy(\":root{\\n\"+PAL.map((p,i)=>`  --couleur-${i+1}: ${p.hex}; \/* ${p.name} *\/`).join(\"\\n\")+\"\\n}\",\"Variables CSS copi\u00e9es\");\n  $(\"#ctalink\").href=CONFIG.contactUrl;\n}\nfunction readFile(f){ if(!\/^image\\\/\/.test(f.type)){ toast(\"Choisis une image (JPG, PNG, WebP\u2026)\"); return } const url=URL.createObjectURL(f); const img=new Image();\n  img.onload=()=>{ loadImage(img,f.name.replace(\/\\.[^.]+$\/,\"\").replace(\/[-_]+\/g,\" \").replace(\/^\\w\/,c=>c.toUpperCase()).slice(0,40)); URL.revokeObjectURL(url) };\n  img.onerror=()=>toast(\"Cette image ne peut pas \u00eatre lue par le navigateur.\"); img.src=url }\nfunction useSample(i){ const s=SAMPLES[i]; pc.width=640; pc.height=480; $(\"#photo\").style.aspectRatio=\"4\/3\"; s.draw(px,640,480); grain(px,640,480); analyse(s.name,s.title) }\n\n\/* iframe auto-height *\/\n(function(){ if(window.parent===window) return; let last=0; const send=()=>{ const h=Math.ceil($(\"#okk\").getBoundingClientRect().height); if(Math.abs(h-last)>2){ last=h; try{ parent.postMessage({type:\"ok3d-palette:height\",height:h},\"*\") }catch(e){} } }; try{ new ResizeObserver(send).observe($(\"#okk\")) }catch(e){ setInterval(send,800) } })();\n\/* Pleine largeur, m\u00eame dans une colonne Elementor limit\u00e9e.\n   Aucune modification globale du th\u00e8me ou du d\u00e9filement de la page. *\/\n(function setupElementorScreen() {\n  let pending = 0;\n  const isFull = () => (document.fullscreenElement || document.webkitFullscreenElement) === OKK || OKK.classList.contains('is-app-screen');\n  const setVar = (name, value) => {\n    if (OKK.style.getPropertyValue(name) !== value) OKK.style.setProperty(name, value);\n  };\n  const measure = () => {\n    pending = 0;\n    if (!OKK.isConnected) { if (observer) observer.disconnect(); return; }\n    const full = isFull();\n    if (!full) {\n      const width = document.documentElement.clientWidth;\n      const shift = parseFloat(OKK.style.getPropertyValue('--okk-shift')) || 0;\n      const left = OKK.getBoundingClientRect().left;\n      setVar('--okk-width', width + 'px');\n      setVar('--okk-shift', (Math.round((shift - left) * 100) \/ 100) + 'px');\n    }\n    const admin = document.getElementById('wpadminbar');\n    const adminVisible = admin && getComputedStyle(admin).position === 'fixed';\n    setVar('--okk-admin-height', (!full && adminVisible ? admin.getBoundingClientRect().height : 0) + 'px');\n  };\n  const schedule = () => { if (!pending) pending = requestAnimationFrame(measure); };\n  const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null;\n  if (observer) {\n    observer.observe(document.documentElement);\n    if (OKK.parentElement) observer.observe(OKK.parentElement);\n  }\n  window.addEventListener('resize', schedule);\n  window.addEventListener('orientationchange', schedule);\n  window.addEventListener('load', schedule, { once: true });\n  if (window.visualViewport) window.visualViewport.addEventListener('resize', schedule);\n  if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);\n  (function setupOK3DFullscreen(root) {\n  const doc = root.ownerDocument;\n  const win = doc.defaultView;\n  let appState = null;\n  const nativeFull = () => (doc.fullscreenElement || doc.webkitFullscreenElement) === root;\n  const active = () => nativeFull() || root.classList.contains('is-app-screen');\n  const attribute = (node, name, value) => {\n    if (node.getAttribute(name) !== value) node.setAttribute(name, value);\n  };\n  const sync = () => {\n    const full = active();\n    root.querySelectorAll('[data-okk-fullscreen]').forEach(button => {\n      const label = full ? '\u2922 Quitter le plein \u00e9cran' : '\u26f6 Plein \u00e9cran';\n      if (button.textContent !== label) button.textContent = label;\n      button.hidden = false;\n      attribute(button, 'aria-pressed', String(full));\n      attribute(button, 'aria-label', full ? 'Quitter le plein \u00e9cran' : 'Afficher en plein \u00e9cran');\n      attribute(button, 'title', full ? 'Quitter le plein \u00e9cran (\u00c9chap)' : 'Afficher en plein \u00e9cran');\n    });\n  };\n  const changed = () => {\n    sync();\n    win.dispatchEvent(new Event('resize'));\n  };\n  const saveOverflow = node => [node.style.getPropertyValue('overflow'), node.style.getPropertyPriority('overflow')];\n  const restoreOverflow = (node, value) => {\n    if (value[0]) node.style.setProperty('overflow', value[0], value[1]);\n    else node.style.removeProperty('overflow');\n  };\n  const focusButton = () => {\n    const button = root.querySelector('[data-okk-fullscreen]');\n    if (button) button.focus({ preventScroll: true });\n  };\n  const enterApp = () => {\n    if (appState) return;\n    const marker = doc.createComment('OK3D \u2014 retour du mode app');\n    appState = {\n      marker, parent: root.parentNode, next: root.nextSibling,\n      bodyOverflow: saveOverflow(doc.body), htmlOverflow: saveOverflow(doc.documentElement),\n      x: win.scrollX, y: win.scrollY\n    };\n    root.before(marker);\n    \/\/ Sortir des conteneurs transform\u00e9s d'Elementor pour couvrir toute la fen\u00eatre.\n    doc.body.appendChild(root);\n    root.classList.add('is-app-screen');\n    doc.body.style.setProperty('overflow', 'hidden', 'important');\n    doc.documentElement.style.setProperty('overflow', 'hidden', 'important');\n    root.scrollTop = 0;\n    changed();\n    focusButton();\n  };\n  const leaveApp = () => {\n    if (!appState) return;\n    const saved = appState;\n    appState = null;\n    root.classList.remove('is-app-screen');\n    if (saved.marker.isConnected) saved.marker.replaceWith(root);\n    else if (saved.parent && saved.parent.isConnected) {\n      saved.parent.insertBefore(root, saved.next && saved.next.parentNode === saved.parent ? saved.next : null);\n    }\n    restoreOverflow(doc.body, saved.bodyOverflow);\n    restoreOverflow(doc.documentElement, saved.htmlOverflow);\n    changed();\n    win.scrollTo(saved.x, saved.y);\n    focusButton();\n  };\n  root.addEventListener('click', async event => {\n    const button = event.target.closest && event.target.closest('[data-okk-fullscreen]');\n    if (!button || !root.contains(button)) return;\n    event.preventDefault();\n    if (appState) { leaveApp(); return; }\n    if (nativeFull()) {\n      const exit = doc.exitFullscreen || doc.webkitExitFullscreen;\n      if (exit) {\n        try { await exit.call(doc); } catch (_) { attribute(button, 'title', 'Appuie sur \u00c9chap pour quitter le plein \u00e9cran'); }\n      }\n      return;\n    }\n    const request = root.requestFullscreen || root.webkitRequestFullscreen;\n    if (request) {\n      try { await request.call(root); sync(); return; } catch (_) { \/* Aper\u00e7u ou appareil sans API : mode app. *\/ }\n    }\n    enterApp();\n  });\n  doc.addEventListener('keydown', event => {\n    if (event.key === 'Escape' && appState) {\n      event.preventDefault();\n      event.stopPropagation();\n      leaveApp();\n      return;\n    }\n    if (event.key !== 'Tab' || !active()) return;\n    const items = Array.from(root.querySelectorAll('button:not(:disabled),a[href],input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex=\"0\"]'))\n      .filter(node => !node.hidden && node.getClientRects().length);\n    if (!items.length) return;\n    const first = items[0], last = items[items.length - 1];\n    if (event.shiftKey && (doc.activeElement === first || !root.contains(doc.activeElement))) {\n      event.preventDefault(); last.focus();\n    } else if (!event.shiftKey && (doc.activeElement === last || !root.contains(doc.activeElement))) {\n      event.preventDefault(); first.focus();\n    }\n  }, true);\n  doc.addEventListener('fullscreenchange', changed);\n  doc.addEventListener('webkitfullscreenchange', changed);\n  \/\/ Les barres de jeu (Keystroke notamment) sont reconstruites apr\u00e8s chaque s\u00e9rie.\n  if (typeof MutationObserver !== 'undefined') new MutationObserver(sync).observe(root, { childList: true, subtree: true });\n  sync();\n  return { active };\n})(OKK);\n  measure();\n})();\n\n\nui();\n(document.fonts&&document.fonts.load?Promise.all([\"800 80px Unbounded\",\"800 80px Geist\",\"600 20px 'Geist Mono'\"].map(f=>document.fonts.load(f).catch(()=>{}))):Promise.resolve()).then(()=>useSample(0));\n\n})();\n<\/script>\t\t\t\t<\/div>\n\t\t\n<\/div>\n\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u26f6 Plein \u00e9cran OK3D Lab \u00b7 Couleur Ta photo.Ta palette. Ton affiche. D\u00e9pose une photo : on en extrait les couleurs qui comptent, on leur donne un nom, et on compose une affiche avec. Tout se passe dans ton navigateur. 01Une photo026 couleurs03Une affiche D\u00e9pose ta photo iciou touche pour en choisir une \u00b7 JPG, [&hellip;]<\/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-15708","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/pages\/15708","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/comments?post=15708"}],"version-history":[{"count":6,"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/pages\/15708\/revisions"}],"predecessor-version":[{"id":15714,"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/pages\/15708\/revisions\/15714"}],"wp:attachment":[{"href":"https:\/\/www.ok3d.fr\/en\/wp-json\/wp\/v2\/media?parent=15708"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}