Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5
4 changed files with 147 additions and 21 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.4.63-beta
|
0.4.64-beta
|
||||||
|
|
|
||||||
|
|
@ -768,15 +768,12 @@
|
||||||
}
|
}
|
||||||
.lg-snd.off{opacity:.55;}
|
.lg-snd.off{opacity:.55;}
|
||||||
/* small digit blocks/slots for the maths widgets */
|
/* small digit blocks/slots for the maths widgets */
|
||||||
.blok.sm{width:46px; height:74px; border-radius:10px; padding-top:4px;}
|
.blok.sm{--dot:7px; width:46px; height:74px; border-radius:10px; padding-top:4px;}
|
||||||
.blok.sm .bl{font-size:24px; height:30px; color:var(--purple);}
|
.blok.sm .bl{font-size:24px; height:30px; color:var(--purple);}
|
||||||
.blok.sm .dots,.slot.sm .dots{grid-template-columns:repeat(2,7px); grid-template-rows:repeat(3,7px); gap:2.5px;}
|
|
||||||
.blok.sm .dots i,.slot.sm .dots i{width:7px; height:7px;}
|
|
||||||
.blok.sm .blok-dots{margin-bottom:7px;}
|
.blok.sm .blok-dots{margin-bottom:7px;}
|
||||||
.slot.sm{width:46px; height:74px; padding-top:4px;}
|
.slot.sm{--dot:7px; width:46px; height:74px; padding-top:4px;}
|
||||||
.slot.sm .ghost{font-size:22px; height:26px;}
|
.slot.sm .ghost{font-size:22px; height:26px;}
|
||||||
.slot.sm .slot-dots{margin-bottom:6px;}
|
.slot.sm .slot-dots{margin-bottom:6px;}
|
||||||
.slot.sm .dots i.off{width:6px; height:6px;}
|
|
||||||
.mt-slots{display:flex; gap:8px; justify-content:center;}
|
.mt-slots{display:flex; gap:8px; justify-content:center;}
|
||||||
.mt-tray{gap:8px; padding:10px 8px; min-height:96px;}
|
.mt-tray{gap:8px; padding:10px 8px; min-height:96px;}
|
||||||
.mt.blocks .mt-a,.mt.blocks .mt-pad{display:none;}
|
.mt.blocks .mt-a,.mt.blocks .mt-pad{display:none;}
|
||||||
|
|
@ -827,6 +824,7 @@
|
||||||
.lg-pic img{width:80px; height:80px; object-fit:contain; border-radius:12px; vertical-align:middle;}
|
.lg-pic img{width:80px; height:80px; object-fit:contain; border-radius:12px; vertical-align:middle;}
|
||||||
.lg-slots{display:flex; gap:10px;}
|
.lg-slots{display:flex; gap:10px;}
|
||||||
.slot{
|
.slot{
|
||||||
|
--dot:9px;
|
||||||
width:64px; height:98px; border-radius:12px; position:relative;
|
width:64px; height:98px; border-radius:12px; position:relative;
|
||||||
background:#e9eef5; border:2.5px dashed #b9c7d9;
|
background:#e9eef5; border:2.5px dashed #b9c7d9;
|
||||||
display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
|
display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
|
||||||
|
|
@ -842,6 +840,7 @@
|
||||||
40%{transform:translateX(7px);} 60%{transform:translateX(-5px);} 80%{transform:translateX(5px);}
|
40%{transform:translateX(7px);} 60%{transform:translateX(-5px);} 80%{transform:translateX(5px);}
|
||||||
}
|
}
|
||||||
.blok{
|
.blok{
|
||||||
|
--dot:9px;
|
||||||
width:64px; height:98px; border-radius:12px; cursor:grab; touch-action:none;
|
width:64px; height:98px; border-radius:12px; cursor:grab; touch-action:none;
|
||||||
display:flex; flex-direction:column; align-items:center; padding-top:6px;
|
display:flex; flex-direction:column; align-items:center; padding-top:6px;
|
||||||
background:linear-gradient(#fdf3dd,var(--wood)); border:2.5px solid var(--wood-d);
|
background:linear-gradient(#fdf3dd,var(--wood)); border:2.5px solid var(--wood-d);
|
||||||
|
|
@ -852,17 +851,30 @@
|
||||||
.blok .bl{font-size:34px; font-weight:900; height:44px; display:flex; align-items:center;}
|
.blok .bl{font-size:34px; font-weight:900; height:44px; display:flex; align-items:center;}
|
||||||
.blok.vowel .bl{color:var(--red);}
|
.blok.vowel .bl{color:var(--red);}
|
||||||
.blok.cons .bl{color:var(--blue);}
|
.blok.cons .bl{color:var(--blue);}
|
||||||
.blok .blok-dots{margin-top:auto; margin-bottom:10px;}
|
.blok .blok-dots{margin-top:auto; margin-bottom:8px;}
|
||||||
|
/* Vangnet: het stippenraster houdt altijd zijn maat, het label geeft mee als
|
||||||
|
een blokformaat krap is. Zonder min-height:0 kan een kolom-flexitem niet
|
||||||
|
onder zijn inhoud krimpen en werd het raster het blok uit geduwd. */
|
||||||
|
.blok .bl,.slot .ghost{min-height:0;}
|
||||||
|
.blok .blok-dots,.slot .slot-dots{flex:none;}
|
||||||
.blok.in-slot{cursor:pointer; animation:pop .3s;}
|
.blok.in-slot{cursor:pointer; animation:pop .3s;}
|
||||||
.blok.dragging{opacity:.25;}
|
.blok.dragging{opacity:.25;}
|
||||||
@keyframes pop{0%{transform:scale(.6);} 60%{transform:scale(1.12);} 100%{transform:scale(1);}}
|
@keyframes pop{0%{transform:scale(.6);} 60%{transform:scale(1.12);} 100%{transform:scale(1);}}
|
||||||
.slot .slot-dots{margin-top:auto; margin-bottom:9px;}
|
.slot .slot-dots{margin-top:auto; margin-bottom:8px;}
|
||||||
.dots{display:grid; grid-template-columns:repeat(2,9px); grid-template-rows:repeat(3,9px); gap:3px;}
|
/* Stippenraster (braillepatroon, zie dotGrid() in data.js). De maat komt uit
|
||||||
.dots i{width:9px; height:9px; border-radius:50%; background:transparent;}
|
één variabele --dot die elk bloktype naast zijn eigen hoogte zet; zo kan een
|
||||||
|
nieuw blokformaat niet meer per ongeluk het raster van een ander formaat
|
||||||
|
erven (dat gebeurde bij de geldblokken en in de smalle-widget-query, waar de
|
||||||
|
stippen onder het blok uit staken). place-self houdt de kleinere "off"-stip
|
||||||
|
gecentreerd in zijn cel - anders staat die links-boven en oogt het patroon
|
||||||
|
scheef. Vuistregel: --dot nooit onder 6px (leesbaarheid); past het niet,
|
||||||
|
dan geeft de labelhoogte of de blokhoogte mee, niet de stip. */
|
||||||
|
.dots{display:grid; grid-template-columns:repeat(2,var(--dot)); grid-template-rows:repeat(3,var(--dot)); gap:calc(var(--dot)/3);}
|
||||||
|
.dots i{width:var(--dot); height:var(--dot); border-radius:50%; background:transparent; place-self:center;}
|
||||||
.blok .dots i.on{background:#b98a3c; box-shadow:inset 0 -1.5px 2px rgba(0,0,0,.35), 0 1px 0 rgba(255,255,255,.5);}
|
.blok .dots i.on{background:#b98a3c; box-shadow:inset 0 -1.5px 2px rgba(0,0,0,.35), 0 1px 0 rgba(255,255,255,.5);}
|
||||||
.blok .dots i.off{background:rgba(0,0,0,.05);}
|
.blok .dots i.off{background:rgba(0,0,0,.05);}
|
||||||
.slot .dots i.on{background:#aebcce; box-shadow:inset 0 2px 3px rgba(30,50,80,.4);}
|
.slot .dots i.on{background:#aebcce; box-shadow:inset 0 2px 3px rgba(30,50,80,.4);}
|
||||||
.slot .dots i.off{background:transparent; border:1px dotted #ccd7e4; width:7px; height:7px;}
|
.slot .dots i.off{background:transparent; border:1px dotted #ccd7e4; width:calc(var(--dot) - 2px); height:calc(var(--dot) - 2px);}
|
||||||
.lg-tray{
|
.lg-tray{
|
||||||
display:flex; gap:12px; justify-content:center; flex-wrap:wrap;
|
display:flex; gap:12px; justify-content:center; flex-wrap:wrap;
|
||||||
background:var(--surface-2); border-radius:16px; padding:14px 12px; min-height:130px; flex:none;
|
background:var(--surface-2); border-radius:16px; padding:14px 12px; min-height:130px; flex:none;
|
||||||
|
|
@ -1885,7 +1897,7 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2
|
||||||
.money-tray-wrap{width:100%;display:flex;flex-direction:column;align-items:center;gap:8px}.money-tray-wrap>b{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.4px}.money-cash-tray{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;padding:11px;border:1px solid var(--line);border-radius:16px;background:var(--surface-2)}
|
.money-tray-wrap{width:100%;display:flex;flex-direction:column;align-items:center;gap:8px}.money-tray-wrap>b{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.4px}.money-cash-tray{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;padding:11px;border:1px solid var(--line);border-radius:16px;background:var(--surface-2)}
|
||||||
.money-count-task{width:100%;min-height:128px;display:grid;place-items:center;padding:14px;border-radius:17px;background:linear-gradient(145deg,#eef7e9,var(--surface-2));border:1px solid var(--line)}.money-count-task .money-piece{transform:rotate(var(--money-tilt,0deg))}
|
.money-count-task{width:100%;min-height:128px;display:grid;place-items:center;padding:14px;border-radius:17px;background:linear-gradient(145deg,#eef7e9,var(--surface-2));border:1px solid var(--line)}.money-count-task .money-piece{transform:rotate(var(--money-tilt,0deg))}
|
||||||
.money-shop-task{width:100%;display:grid;grid-template-columns:minmax(0,1fr) minmax(150px,.8fr);gap:12px}.money-product,.money-paid{min-height:150px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:12px;border:1px solid var(--line);border-radius:17px;background:var(--surface-2)}.money-product-icon{font-size:clamp(52px,8vw,70px);line-height:1;filter:drop-shadow(0 5px 5px rgba(30,45,70,.2))}.money-product-name{max-width:100%;font-size:17px;font-weight:950;text-align:center}.money-product>b,.money-paid>b{color:var(--muted);font-size:12px;text-transform:uppercase}.money-product .money-task-amount{font-size:37px}.money-product .money-task-words{font-size:19px}.money-paid>output{font-size:31px;font-weight:950;color:var(--blue)}
|
.money-shop-task{width:100%;display:grid;grid-template-columns:minmax(0,1fr) minmax(150px,.8fr);gap:12px}.money-product,.money-paid{min-height:150px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:12px;border:1px solid var(--line);border-radius:17px;background:var(--surface-2)}.money-product-icon{font-size:clamp(52px,8vw,70px);line-height:1;filter:drop-shadow(0 5px 5px rgba(30,45,70,.2))}.money-product-name{max-width:100%;font-size:17px;font-weight:950;text-align:center}.money-product>b,.money-paid>b{color:var(--muted);font-size:12px;text-transform:uppercase}.money-product .money-task-amount{font-size:37px}.money-product .money-task-words{font-size:19px}.money-paid>output{font-size:31px;font-weight:950;color:var(--blue)}
|
||||||
.money-digit-answer,.money-type-answer{width:min(100%,300px);display:flex;flex-direction:column;align-items:center;gap:10px}.money-digit-answer>p,.money-type-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.money-answer-slots{display:flex;align-items:center;justify-content:center;gap:6px}.money-answer-slots>strong{font-size:29px}.money-digit-slot.slot.sm{width:43px;height:64px;padding-top:1px}.money-digit-slot.slot.sm .ghost{height:24px;font-size:20px}.money-digit-slot.slot.sm .slot-dots{margin-bottom:3px}.money-digit-slot.filled{padding:0}.money-digit-slot.fixed{border-style:solid;background:var(--surface-2);opacity:.72}.money-digit-block.blok{width:39px;height:58px;min-height:58px;padding-top:2px;cursor:default}.money-digit-block.blok .bl{height:28px;font-size:25px;color:var(--purple)}.money-digit-block.blok .blok-dots{margin-bottom:4px}.money-digit-tray.lg-tray{display:grid;grid-template-columns:repeat(5,39px);gap:7px;min-height:0;padding:11px 14px}.money-digit-tray .money-drag-digit{cursor:grab}.money-digit-tray .money-drag-digit:active{cursor:grabbing}.money-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
.money-digit-answer,.money-type-answer{width:min(100%,300px);display:flex;flex-direction:column;align-items:center;gap:10px}.money-digit-answer>p,.money-type-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.money-answer-slots{display:flex;align-items:center;justify-content:center;gap:6px}.money-answer-slots>strong{font-size:29px}.money-digit-slot.slot.sm{width:43px;height:64px;padding-top:1px}.money-digit-slot.slot.sm .ghost{height:24px;font-size:20px}.money-digit-slot.slot.sm .slot-dots{margin-bottom:3px}.money-digit-slot.filled{padding:0}.money-digit-slot.fixed{border-style:solid;background:var(--surface-2);opacity:.72}.money-digit-block.blok,.money-digit-slot.slot.sm{--dot:6px}.money-digit-block.blok{width:39px;height:62px;min-height:62px;padding-top:2px;cursor:default}.money-digit-block.blok .bl{height:26px;font-size:25px;color:var(--purple)}.money-digit-block.blok .blok-dots{margin-bottom:3px}.money-digit-tray.lg-tray{display:grid;grid-template-columns:repeat(5,39px);gap:7px;min-height:0;padding:11px 14px}.money-digit-tray .money-drag-digit{cursor:grab}.money-digit-tray .money-drag-digit:active{cursor:grabbing}.money-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
||||||
.money-type-display{min-width:190px;padding:9px 14px;border:3px dashed var(--line);border-radius:14px;background:var(--surface-2);font-size:34px;font-weight:950;letter-spacing:2px;color:var(--green)}.money-keypad{width:100%;display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.money-keypad .tbtn{min-width:0;padding:0;font-size:20px}
|
.money-type-display{min-width:190px;padding:9px 14px;border:3px dashed var(--line);border-radius:14px;background:var(--surface-2);font-size:34px;font-weight:950;letter-spacing:2px;color:var(--green)}.money-keypad{width:100%;display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.money-keypad .tbtn{min-width:0;padding:0;font-size:20px}
|
||||||
.money-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.money-feedback.ok{color:var(--green)}.money-feedback.bad{color:var(--red)}.money-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.money-actions .tbtn:not(.ghost){background:var(--green);color:#fff}
|
.money-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.money-feedback.ok{color:var(--green)}.money-feedback.bad{color:var(--red)}.money-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.money-actions .tbtn:not(.ghost){background:var(--green);color:#fff}
|
||||||
@media(max-width:520px){.money-task-card{gap:9px;padding:10px}.money-level-control>span{display:none}.money-top{top:-10px;gap:5px}.money-shop-task{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:7px}.money-product,.money-paid{min-height:112px;padding:8px 6px;gap:4px}.money-product-icon{font-size:46px}.money-product-name{font-size:14px}.money-product .money-task-amount{font-size:32px}.money-product .money-task-words{font-size:16px}.money-paid>output{font-size:26px}.money-cash-tray{gap:6px;padding:9px}}
|
@media(max-width:520px){.money-task-card{gap:9px;padding:10px}.money-level-control>span{display:none}.money-top{top:-10px;gap:5px}.money-shop-task{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:7px}.money-product,.money-paid{min-height:112px;padding:8px 6px;gap:4px}.money-product-icon{font-size:46px}.money-product-name{font-size:14px}.money-product .money-task-amount{font-size:32px}.money-product .money-task-words{font-size:16px}.money-paid>output{font-size:26px}.money-cash-tray{gap:6px;padding:9px}}
|
||||||
|
|
@ -1972,7 +1984,7 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2
|
||||||
.world-spell-answer{width:100%;display:flex;flex-direction:column;align-items:center;gap:12px}
|
.world-spell-answer{width:100%;display:flex;flex-direction:column;align-items:center;gap:12px}
|
||||||
.world-spell-word{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;max-width:100%}
|
.world-spell-word{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;max-width:100%}
|
||||||
.world-spell-gap{width:14px;flex:none}
|
.world-spell-gap{width:14px;flex:none}
|
||||||
.world-letter-slot.slot.sm,.world-letter-block.blok.sm{width:40px;height:60px;padding-top:3px}
|
.world-letter-slot.slot.sm,.world-letter-block.blok.sm{--dot:6px;width:40px;height:62px;padding-top:3px}
|
||||||
.world-letter-slot.slot.sm .ghost{height:24px;font-size:19px}
|
.world-letter-slot.slot.sm .ghost{height:24px;font-size:19px}
|
||||||
.world-letter-slot.slot.sm .slot-dots{margin-bottom:4px}
|
.world-letter-slot.slot.sm .slot-dots{margin-bottom:4px}
|
||||||
.world-letter-block.blok.sm .bl{height:26px;font-size:22px}
|
.world-letter-block.blok.sm .bl{height:26px;font-size:22px}
|
||||||
|
|
@ -2000,8 +2012,9 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
|
||||||
.clock-hand{position:absolute;z-index:4;left:calc(50% - 5px);bottom:50%;width:10px;border-radius:9px;transform-origin:50% 100%;background:var(--ink);cursor:grab;touch-action:none}.clock-hand:active{cursor:grabbing}.clock-hour{height:28%}.clock-min{left:calc(50% - 4px);width:8px;height:39%;background:var(--pink)}.clock-pin{position:absolute;z-index:5;left:calc(50% - 8px);top:calc(50% - 8px);width:16px;height:16px;border:3px solid var(--surface);border-radius:50%;background:var(--pink);box-shadow:0 1px 4px rgba(0,0,0,.28)}
|
.clock-hand{position:absolute;z-index:4;left:calc(50% - 5px);bottom:50%;width:10px;border-radius:9px;transform-origin:50% 100%;background:var(--ink);cursor:grab;touch-action:none}.clock-hand:active{cursor:grabbing}.clock-hour{height:28%}.clock-min{left:calc(50% - 4px);width:8px;height:39%;background:var(--pink)}.clock-pin{position:absolute;z-index:5;left:calc(50% - 8px);top:calc(50% - 8px);width:16px;height:16px;border:3px solid var(--surface);border-radius:50%;background:var(--pink);box-shadow:0 1px 4px rgba(0,0,0,.28)}
|
||||||
.clock.interactive{touch-action:none;user-select:none;outline:4px solid transparent;transition:outline-color .15s,transform .15s}.clock.interactive:hover,.clock.interactive:focus-within{outline-color:var(--accent-soft)}
|
.clock.interactive{touch-action:none;user-select:none;outline:4px solid transparent;transition:outline-color .15s,transform .15s}.clock.interactive:hover,.clock.interactive:focus-within{outline-color:var(--accent-soft)}
|
||||||
.clock-time-blocks{display:flex;align-items:center;justify-content:center;gap:5px;margin:0;padding:6px;border:0;border-radius:14px;background:transparent;color:var(--ink);font:inherit}.clock-time-blocks:is(button){cursor:pointer}.clock-time-blocks:is(button):hover,.clock-time-blocks:is(button):focus-visible{background:var(--accent-soft);outline:2px solid var(--accent)}.clock-time-blocks>strong{font-size:30px}
|
.clock-time-blocks{display:flex;align-items:center;justify-content:center;gap:5px;margin:0;padding:6px;border:0;border-radius:14px;background:transparent;color:var(--ink);font:inherit}.clock-time-blocks:is(button){cursor:pointer}.clock-time-blocks:is(button):hover,.clock-time-blocks:is(button):focus-visible{background:var(--accent-soft);outline:2px solid var(--accent)}.clock-time-blocks>strong{font-size:30px}
|
||||||
.clock-digit-block.blok{width:42px;height:62px;min-height:62px;padding-top:3px;cursor:default}.clock-digit-block.blok .bl{height:31px;font-size:27px;color:var(--purple)}.clock-digit-block.blok .blok-dots{margin-bottom:5px}.clock-digit-block.blok .dots,.clock-digit-slot .dots{grid-template-columns:repeat(2,6px);grid-template-rows:repeat(3,6px);gap:2px}.clock-digit-block.blok .dots i,.clock-digit-slot .dots i{width:6px;height:6px}
|
.clock-digit-block.blok,.clock-digit-slot.slot.sm{--dot:6px}
|
||||||
.clock-live-answer{padding:6px 12px;border:2px dashed var(--line);border-radius:16px;background:var(--surface-2)}.clock-read-layout{width:100%;display:flex;align-items:center;justify-content:center;gap:16px;flex-wrap:wrap}.clock-read-layout>.clock-assignment{flex:1;min-width:190px}.clock-read-layout .clock{--clock-size:180px}.clock-block-answer{width:min(100%,250px);display:flex;flex-direction:column;align-items:center;gap:10px}.clock-block-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.clock-answer-slots{display:flex;align-items:center;justify-content:center;gap:7px}.clock-digit-slot.slot.sm{width:46px;height:66px;padding-top:2px}.clock-digit-slot.slot.sm .ghost{height:25px;font-size:20px}.clock-digit-slot.slot.sm .slot-dots{margin-bottom:4px}.clock-digit-slot.filled{padding:0}.clock-slot-colon{font-size:30px;margin:0 2px}.clock-block-tray.lg-tray{display:grid;grid-template-columns:repeat(5,38px);gap:7px;min-height:0;padding:11px 14px}.clock-block-tray .clock-drag-block{width:38px;height:56px;min-height:56px;cursor:grab}.clock-block-tray .clock-drag-block:active{cursor:grabbing}.clock-block-tray .clock-drag-block:focus-visible{outline:3px solid var(--accent);outline-offset:2px}.clock-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
.clock-digit-block.blok{width:42px;height:66px;min-height:66px;padding-top:3px;cursor:default}.clock-digit-block.blok .bl{height:29px;font-size:27px;color:var(--purple)}.clock-digit-block.blok .blok-dots{margin-bottom:4px}
|
||||||
|
.clock-live-answer{padding:6px 12px;border:2px dashed var(--line);border-radius:16px;background:var(--surface-2)}.clock-read-layout{width:100%;display:flex;align-items:center;justify-content:center;gap:16px;flex-wrap:wrap}.clock-read-layout>.clock-assignment{flex:1;min-width:190px}.clock-read-layout .clock{--clock-size:180px}.clock-block-answer{width:min(100%,250px);display:flex;flex-direction:column;align-items:center;gap:10px}.clock-block-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.clock-answer-slots{display:flex;align-items:center;justify-content:center;gap:7px}.clock-digit-slot.slot.sm{width:46px;height:66px;padding-top:2px}.clock-digit-slot.slot.sm .ghost{height:25px;font-size:20px}.clock-digit-slot.slot.sm .slot-dots{margin-bottom:4px}.clock-digit-slot.filled{padding:0}.clock-slot-colon{font-size:30px;margin:0 2px}.clock-block-tray.lg-tray{display:grid;grid-template-columns:repeat(5,38px);gap:7px;min-height:0;padding:11px 14px}.clock-block-tray .clock-drag-block{width:38px;height:66px;min-height:66px;cursor:grab}.clock-block-tray .clock-drag-block:active{cursor:grabbing}.clock-block-tray .clock-drag-block:focus-visible{outline:3px solid var(--accent);outline-offset:2px}.clock-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
||||||
.clock-choices{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}.clock-choices.wood .clock-time-blocks{border:1px solid var(--line);background:var(--surface-2)}.clock-word-choice{min-height:48px;border:2px solid var(--wood-d)!important;background:linear-gradient(#fdf3dd,var(--wood))!important;color:var(--ink)!important;box-shadow:0 3px 0 var(--wood-d)}.clock-digital-choice{min-width:118px;min-height:58px;font-size:28px!important;color:var(--blue)!important}.clock-type-answer{width:min(100%,250px);display:flex;flex-direction:column;align-items:center;gap:9px}.clock-type-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.clock-type-display{min-width:180px;padding:9px 14px;border:3px dashed var(--line);border-radius:14px;background:var(--surface-2);font-size:34px;font-weight:950;letter-spacing:3px;color:var(--blue)}.clock-keypad{width:100%;display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.clock-keypad .tbtn{min-width:0;padding:0;font-size:20px}.clock-keypad .ok{background:var(--green);color:#fff}
|
.clock-choices{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}.clock-choices.wood .clock-time-blocks{border:1px solid var(--line);background:var(--surface-2)}.clock-word-choice{min-height:48px;border:2px solid var(--wood-d)!important;background:linear-gradient(#fdf3dd,var(--wood))!important;color:var(--ink)!important;box-shadow:0 3px 0 var(--wood-d)}.clock-digital-choice{min-width:118px;min-height:58px;font-size:28px!important;color:var(--blue)!important}.clock-type-answer{width:min(100%,250px);display:flex;flex-direction:column;align-items:center;gap:9px}.clock-type-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}.clock-type-display{min-width:180px;padding:9px 14px;border:3px dashed var(--line);border-radius:14px;background:var(--surface-2);font-size:34px;font-weight:950;letter-spacing:3px;color:var(--blue)}.clock-keypad{width:100%;display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.clock-keypad .tbtn{min-width:0;padding:0;font-size:20px}.clock-keypad .ok{background:var(--green);color:#fff}
|
||||||
.clock-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.clock-feedback.ok{color:var(--green)}.clock-feedback.bad{color:var(--red)}.clock-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.clock-actions .tbtn:not(.ghost){background:var(--green);color:#fff}.clock-drag-hint{color:var(--muted);font-weight:800;text-align:center}
|
.clock-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.clock-feedback.ok{color:var(--green)}.clock-feedback.bad{color:var(--red)}.clock-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.clock-actions .tbtn:not(.ghost){background:var(--green);color:#fff}.clock-drag-hint{color:var(--muted);font-weight:800;text-align:center}
|
||||||
@media(max-width:480px){.clock-task-card{padding:12px}.clock-level-control>span{display:none}.clock-top{gap:5px}.clock{--clock-size:min(210px,68vw)}.clock-target{min-height:72px}.clock-block-tray.lg-tray{gap:7px;padding:9px}}
|
@media(max-width:480px){.clock-task-card{padding:12px}.clock-level-control>span{display:none}.clock-top{gap:5px}.clock{--clock-size:min(210px,68vw)}.clock-target{min-height:72px}.clock-block-tray.lg-tray{gap:7px;padding:9px}}
|
||||||
|
|
@ -2024,7 +2037,8 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
|
||||||
.place-digit-slot.slot.sm{width:46px;height:66px;padding-top:2px}.place-digit-slot.slot.sm .ghost{height:25px;font-size:20px}.place-digit-slot.slot.sm .slot-dots{margin-bottom:4px}.place-digit-slot.filled{padding:0}
|
.place-digit-slot.slot.sm{width:46px;height:66px;padding-top:2px}.place-digit-slot.slot.sm .ghost{height:25px;font-size:20px}.place-digit-slot.slot.sm .slot-dots{margin-bottom:4px}.place-digit-slot.filled{padding:0}
|
||||||
.place-digit-tray.lg-tray{display:grid;grid-template-columns:repeat(5,38px);gap:7px;min-height:0;padding:11px 14px}.place-digit-tray .place-drag-block{width:38px;height:56px;min-height:56px;cursor:grab}.place-digit-tray .place-drag-block:active{cursor:grabbing}.place-digit-tray .place-drag-block:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
|
.place-digit-tray.lg-tray{display:grid;grid-template-columns:repeat(5,38px);gap:7px;min-height:0;padding:11px 14px}.place-digit-tray .place-drag-block{width:38px;height:56px;min-height:56px;cursor:grab}.place-digit-tray .place-drag-block:active{cursor:grabbing}.place-digit-tray .place-drag-block:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
|
||||||
.place-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
.place-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
|
||||||
.place-digit-block.blok{width:42px;height:62px;min-height:62px;padding-top:3px;cursor:default}.place-digit-block.blok .bl{height:31px;font-size:27px;color:var(--purple)}.place-digit-block.blok .blok-dots{margin-bottom:5px}.place-digit-block.blok .dots,.place-digit-slot .dots{grid-template-columns:repeat(2,6px);grid-template-rows:repeat(3,6px);gap:2px}.place-digit-block.blok .dots i,.place-digit-slot .dots i{width:6px;height:6px}
|
.place-digit-block.blok,.place-digit-slot.slot.sm{--dot:6px}
|
||||||
|
.place-digit-block.blok{width:42px;height:66px;min-height:66px;padding-top:3px;cursor:default}.place-digit-block.blok .bl{height:29px;font-size:27px;color:var(--purple)}.place-digit-block.blok .blok-dots{margin-bottom:4px}
|
||||||
.place-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.place-feedback.ok{color:var(--green)}.place-feedback.bad{color:var(--red)}
|
.place-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.place-feedback.ok{color:var(--green)}.place-feedback.bad{color:var(--red)}
|
||||||
.place-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.place-actions .tbtn:not(.ghost){background:var(--green);color:#fff}
|
.place-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.place-actions .tbtn:not(.ghost){background:var(--green);color:#fff}
|
||||||
@media(max-width:480px){.place-task-card{padding:12px}.place-level-control>span{display:none}.place-top{gap:5px}.place-digit-tray.lg-tray{gap:7px;padding:9px}}
|
@media(max-width:480px){.place-task-card{padding:12px}.place-level-control>span{display:none}.place-top{gap:5px}.place-digit-tray.lg-tray{gap:7px;padding:9px}}
|
||||||
|
|
@ -2239,14 +2253,20 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
|
||||||
.lg,.mt,.dc,.np,.timer,.nlw,.sb{padding:6px}
|
.lg,.mt,.dc,.np,.timer,.nlw,.sb{padding:6px}
|
||||||
.lg-cards{gap:3px}
|
.lg-cards{gap:3px}
|
||||||
.lg-cards button,.lg-snd,.lg-mode{width:32px;height:34px}
|
.lg-cards button,.lg-snd,.lg-mode{width:32px;height:34px}
|
||||||
|
/* smallere blokken: label én stipmaat moeten mee, anders steekt het raster
|
||||||
|
onder het blok uit (dat gebeurde hier met de cijferblokken) */
|
||||||
.lg .slot:not(.clock-digit-slot):not(.money-digit-slot):not(.place-digit-slot),
|
.lg .slot:not(.clock-digit-slot):not(.money-digit-slot):not(.place-digit-slot),
|
||||||
.lg .blok:not(.clock-digit-block):not(.money-digit-block):not(.place-digit-block){width:52px;height:82px}
|
.lg .blok:not(.clock-digit-block):not(.money-digit-block):not(.place-digit-block){--dot:7px;width:52px;height:82px}
|
||||||
|
.lg .blok:not(.clock-digit-block):not(.money-digit-block):not(.place-digit-block) .bl{font-size:28px;height:36px}
|
||||||
|
.lg .slot:not(.clock-digit-slot):not(.money-digit-slot):not(.place-digit-slot) .ghost{font-size:24px;height:32px}
|
||||||
.clock-answer-slots,.money-answer-slots,.place-answer-slots{gap:4px}
|
.clock-answer-slots,.money-answer-slots,.place-answer-slots{gap:4px}
|
||||||
.clock-digit-slot.slot.sm,.money-digit-slot.slot.sm,.place-digit-slot.slot.sm{width:39px;height:60px}
|
.clock-digit-slot.slot.sm,.money-digit-slot.slot.sm,.place-digit-slot.slot.sm{width:39px}
|
||||||
.clock-digit-block.blok,.money-digit-block.blok,.place-digit-block.blok{width:34px;height:52px;min-height:52px}
|
/* de blokhoogte geeft mee (de widget is te smal, niet te laag), zodat de
|
||||||
|
cijfers leesbaar blijven en het raster er alsnog binnen past */
|
||||||
|
.clock-digit-block.blok,.money-digit-block.blok,.place-digit-block.blok{width:34px}
|
||||||
.clock-block-tray.lg-tray,.money-digit-tray.lg-tray,.place-digit-tray.lg-tray{
|
.clock-block-tray.lg-tray,.money-digit-tray.lg-tray,.place-digit-tray.lg-tray{
|
||||||
grid-template-columns:repeat(5,34px);gap:4px;padding:7px}
|
grid-template-columns:repeat(5,34px);gap:4px;padding:7px}
|
||||||
.clock-block-tray .clock-drag-block,.place-digit-tray .place-drag-block{width:34px;height:52px;min-height:52px}
|
.clock-block-tray .clock-drag-block,.place-digit-tray .place-drag-block{width:34px}
|
||||||
.clock-type-display,.money-type-display{min-width:0;width:100%;font-size:30px}
|
.clock-type-display,.money-type-display{min-width:0;width:100%;font-size:30px}
|
||||||
.world-answers{grid-template-columns:1fr}
|
.world-answers{grid-template-columns:1fr}
|
||||||
.world-visual.mixed{gap:5px}
|
.world-visual.mixed{gap:5px}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.4.63-beta";
|
const VERSION = "0.4.64-beta";
|
||||||
/* Safari en oudere mobiele browsers behandelen 100vh/100% soms als de hoogte
|
/* Safari en oudere mobiele browsers behandelen 100vh/100% soms als de hoogte
|
||||||
achter hun adres- en navigatiebalk. visualViewport geeft de werkelijk
|
achter hun adres- en navigatiebalk. visualViewport geeft de werkelijk
|
||||||
zichtbare hoogte; de CSS gebruikt deze pixelwaarde als betrouwbare bron en
|
zichtbare hoogte; de CSS gebruikt deze pixelwaarde als betrouwbare bron en
|
||||||
|
|
|
||||||
106
test/dot-grid.test.js
Normal file
106
test/dot-grid.test.js
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
|
||||||
|
/* Stippenraster (braillepatroon uit dotGrid(), data.js). Het ging mis doordat de
|
||||||
|
stipmaat per bloktype hardgecodeerd was terwijl blokhoogte, labelhoogte en
|
||||||
|
marges óók per type verschillen: de geldblokken erfden de maat van een ander
|
||||||
|
formaat en de smalle-widget-query verkleinde alleen de blokken. Gevolg: de
|
||||||
|
stippen staken onder het blok uit. Deze test bewaakt de regels die dat
|
||||||
|
voorkomen; de volledige geometrie is daarnaast in de browser opgemeten. */
|
||||||
|
|
||||||
|
const CSS = await readFile('public/css/teach.css', 'utf8');
|
||||||
|
|
||||||
|
/* alle regels als [selector, declaraties]; commentaar eruit want daar staan
|
||||||
|
voorbeelden in die anders als declaratie meetellen */
|
||||||
|
function rules(css) {
|
||||||
|
const clean = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
||||||
|
const out = [];
|
||||||
|
for (const chunk of clean.split('}')) {
|
||||||
|
const i = chunk.lastIndexOf('{');
|
||||||
|
if (i < 0) continue;
|
||||||
|
const selector = chunk.slice(0, i).split('{').pop().trim();
|
||||||
|
if (!selector || selector.startsWith('@')) continue;
|
||||||
|
out.push([selector, chunk.slice(i + 1)]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
const RULES = rules(CSS);
|
||||||
|
const declares = (decls, prop) => new RegExp(`(^|;|\\s)${prop}\\s*:`).test(decls);
|
||||||
|
|
||||||
|
/* het bloktype in een selector: .money-digit-block, .blok.sm, .blok, ... */
|
||||||
|
function contextTokens(selector) {
|
||||||
|
return selector.split(',')
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.filter((part) => /\.(blok|slot)\b/.test(part) && !/\s\.(bl|ghost|dots|blok-dots|slot-dots)\b/.test(part))
|
||||||
|
.map((part) => (part.match(/\.[a-z0-9-]*(?:block|slot|blok)\b/g) || []).join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('elk bloktype dat een eigen hoogte krijgt, zet ook zijn eigen stipmaat', () => {
|
||||||
|
const withDot = new Set();
|
||||||
|
RULES.forEach(([selector, decls]) => {
|
||||||
|
if (declares(decls, '--dot')) contextTokens(selector).forEach((t) => withDot.add(t));
|
||||||
|
});
|
||||||
|
const missing = [];
|
||||||
|
RULES.forEach(([selector, decls]) => {
|
||||||
|
if (!declares(decls, 'height')) return;
|
||||||
|
contextTokens(selector).forEach((token) => {
|
||||||
|
if (!token) return;
|
||||||
|
// een variant erft de maat van zijn basis (.blok/.slot) als die het zet
|
||||||
|
const base = token.includes('slot') ? '.slot' : '.blok';
|
||||||
|
if (!withDot.has(token) && !withDot.has(base)) missing.push(`${selector} (${token})`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
assert.deepEqual(missing, [], 'deze regels zetten een blokhoogte zonder dat er ergens een --dot voor dat type bestaat');
|
||||||
|
assert.ok(withDot.has('.blok') && withDot.has('.slot'), 'basisblok en -slot zetten --dot');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('de stippen zijn nooit kleiner dan 6px: het patroon moet leesbaar blijven', () => {
|
||||||
|
const values = [...CSS.matchAll(/--dot\s*:\s*([\d.]+)px/g)].map((m) => Number(m[1]));
|
||||||
|
assert.ok(values.length >= 5, `verwacht meerdere stipmaten, gevonden: ${values.length}`);
|
||||||
|
values.forEach((v) => assert.ok(v >= 6, `stipmaat ${v}px is onder de leesbaarheidsgrens`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('geen enkel bloktype heeft nog een eigen hardgecodeerd stippenraster', () => {
|
||||||
|
const hardcoded = RULES.filter(([selector, decls]) =>
|
||||||
|
/\.dots\b/.test(selector)
|
||||||
|
&& /(grid-template-(columns|rows)\s*:[^;]*\d+px|(?:^|;|\s)(width|height)\s*:\s*\d+px)/.test(decls));
|
||||||
|
assert.deepEqual(hardcoded.map(([s]) => s), [],
|
||||||
|
'stipmaten horen via var(--dot) te lopen, anders loopt een nieuw blokformaat weer uit de pas');
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Narekening van de verticale begroting per bloktype. De blokrand telt mee
|
||||||
|
(border-box), dus: rand + padding-top + labelhoogte + rasterhoogte +
|
||||||
|
ondermarge moet binnen de blokhoogte passen. Rasterhoogte = 3 stippen + 2
|
||||||
|
tussenruimtes van --dot/3. */
|
||||||
|
const CONTEXTS = [
|
||||||
|
{ naam: 'letterblok', maat: '.blok', label: '.blok .bl', marge: '.blok .blok-dots', dot: '.blok' },
|
||||||
|
{ naam: 'letterslot', maat: '.slot', label: '.slot .ghost', marge: '.slot .slot-dots', dot: '.slot' },
|
||||||
|
{ naam: 'rekenblok', maat: '.blok.sm', label: '.blok.sm .bl', marge: '.blok.sm .blok-dots', dot: '.blok.sm' },
|
||||||
|
{ naam: 'rekenslot', maat: '.slot.sm', label: '.slot.sm .ghost', marge: '.slot.sm .slot-dots', dot: '.slot.sm' },
|
||||||
|
];
|
||||||
|
function value(selectorPart, prop) {
|
||||||
|
let found = null;
|
||||||
|
RULES.forEach(([selector, decls]) => {
|
||||||
|
if (!selector.split(',').some((part) => part.trim() === selectorPart)) return;
|
||||||
|
const m = decls.match(new RegExp(`(?:^|;|\\s)${prop}\\s*:\\s*([\\d.]+)px`));
|
||||||
|
if (m) found = Number(m[1]);
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('label, stippenraster en marges passen samen binnen elk basisblok', () => {
|
||||||
|
CONTEXTS.forEach((ctx) => {
|
||||||
|
const hoogte = value(ctx.maat, 'height');
|
||||||
|
const padding = value(ctx.maat, 'padding-top') ?? 0;
|
||||||
|
const rand = (value(ctx.maat, 'border') ?? 2.5) * 2;
|
||||||
|
const label = value(ctx.label, 'height');
|
||||||
|
const marge = value(ctx.marge, 'margin-bottom');
|
||||||
|
const dot = value(ctx.dot, '--dot');
|
||||||
|
assert.ok(hoogte && label && marge && dot, `${ctx.naam}: maten niet gevonden`);
|
||||||
|
const raster = 3 * dot + 2 * (dot / 3);
|
||||||
|
const nodig = padding + label + raster + marge + rand;
|
||||||
|
assert.ok(nodig <= hoogte,
|
||||||
|
`${ctx.naam}: ${nodig.toFixed(1)}px nodig in een blok van ${hoogte}px - de stippen steken eruit`);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue