fix: lijn het stippenpatroon uit binnen elk blokformaat (v0.4.64-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 50s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 50s
Gewijzigde functionaliteiten: - De open stippen in de doelvakken stonden links-boven in hun rastercel in plaats van gecentreerd, waardoor het patroon scheef oogde; ze staan nu op dezelfde as als de gevulde stippen. - De stippen van de geldblokken en van alle cijferblokken in een smalgemaakte widget staken onder het blok uit. De stipmaat komt nu uit een variabele die elk bloktype naast zijn eigen hoogte zet, in plaats van uit losse hardgecodeerde regels per widget. - De smalle-widget-weergave verkleint alleen nog de breedte; blokhoogtes blijven gelijk, want juist het verkleinen van de hoogte duwde het raster naar buiten. Letterblokken krijgen daar een passend kleiner cijfer/letterformaat. - Nieuw vangnet: het raster houdt altijd zijn maat en het label geeft mee als een blokformaat krap is, zodat een nieuw blokformaat dit niet opnieuw kan veroorzaken. Tests: 196 geslaagd (4 nieuw in test/dot-grid.test.js; die vier vallen ook daadwerkelijk om op de oude CSS). Daarnaast in de browser opgemeten voor alle zeven blokcontexten, bij normale en smalle widgetbreedte, plus screenshots en een controle in de echte leerlingweergave. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126hjBPD2ggZ2LEoDYZSW7S
This commit is contained in:
parent
50b9f32e91
commit
fcb117eb54
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;}
|
||||
/* 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 .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;}
|
||||
.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 .slot-dots{margin-bottom:6px;}
|
||||
.slot.sm .dots i.off{width:6px; height:6px;}
|
||||
.mt-slots{display:flex; gap:8px; justify-content:center;}
|
||||
.mt-tray{gap:8px; padding:10px 8px; min-height:96px;}
|
||||
.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-slots{display:flex; gap:10px;}
|
||||
.slot{
|
||||
--dot:9px;
|
||||
width:64px; height:98px; border-radius:12px; position:relative;
|
||||
background:#e9eef5; border:2.5px dashed #b9c7d9;
|
||||
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);}
|
||||
}
|
||||
.blok{
|
||||
--dot:9px;
|
||||
width:64px; height:98px; border-radius:12px; cursor:grab; touch-action:none;
|
||||
display:flex; flex-direction:column; align-items:center; padding-top:6px;
|
||||
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.vowel .bl{color:var(--red);}
|
||||
.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.dragging{opacity:.25;}
|
||||
@keyframes pop{0%{transform:scale(.6);} 60%{transform:scale(1.12);} 100%{transform:scale(1);}}
|
||||
.slot .slot-dots{margin-top:auto; margin-bottom:9px;}
|
||||
.dots{display:grid; grid-template-columns:repeat(2,9px); grid-template-rows:repeat(3,9px); gap:3px;}
|
||||
.dots i{width:9px; height:9px; border-radius:50%; background:transparent;}
|
||||
.slot .slot-dots{margin-top:auto; margin-bottom:8px;}
|
||||
/* Stippenraster (braillepatroon, zie dotGrid() in data.js). De maat komt uit
|
||||
éé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.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.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{
|
||||
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;
|
||||
|
|
@ -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-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-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-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}}
|
||||
|
|
@ -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-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-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 .slot-dots{margin-bottom:4px}
|
||||
.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.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-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-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,.clock-digit-slot.slot.sm{--dot:6px}
|
||||
.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-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}}
|
||||
|
|
@ -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-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-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-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}}
|
||||
|
|
@ -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-cards{gap:3px}
|
||||
.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 .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-digit-slot.slot.sm,.money-digit-slot.slot.sm,.place-digit-slot.slot.sm{width:39px;height:60px}
|
||||
.clock-digit-block.blok,.money-digit-block.blok,.place-digit-block.blok{width:34px;height:52px;min-height:52px}
|
||||
.clock-digit-slot.slot.sm,.money-digit-slot.slot.sm,.place-digit-slot.slot.sm{width:39px}
|
||||
/* 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{
|
||||
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}
|
||||
.world-answers{grid-template-columns:1fr}
|
||||
.world-visual.mixed{gap:5px}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"use strict";
|
||||
/* 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. */
|
||||
const VERSION = "0.4.63-beta";
|
||||
const VERSION = "0.4.64-beta";
|
||||
/* Safari en oudere mobiele browsers behandelen 100vh/100% soms als de hoogte
|
||||
achter hun adres- en navigatiebalk. visualViewport geeft de werkelijk
|
||||
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