@kolkrabbi/kol-theme 0.42.2 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/kol-color.css CHANGED
@@ -92,6 +92,20 @@
92
92
  --kol-border-default: color-mix(in srgb, var(--kol-surface-on-primary) 8%, transparent);
93
93
  --kol-border-focus: color-mix(in srgb, var(--kol-accent-primary) 70%, var(--kol-surface-on-primary));
94
94
  --kol-focus-ring: var(--kol-accent-primary);
95
+ /* The QUIET ring — 1px, inset, for dense rows where the loud ring's 2px
96
+ * offset would bloom over its neighbours (nav rail leaves today).
97
+ *
98
+ * It exists so the pair is a real OFF SWITCH (FocusRingsInConsumers, kol-fxr
99
+ * 2026-08-15, user ruling): --kol-focus-ring alone was only ever half of one,
100
+ * because the rails hardcoded --kol-fg-32 and never read it, so a consumer
101
+ * setting it transparent lost button rings and kept rail rings. Set BOTH to
102
+ * transparent and the system is genuinely off — no `outline: none !important`
103
+ * in consumer CSS, which is the local override this replaces.
104
+ *
105
+ * Default is --kol-fg-32, exactly what the rails already drew, so minting
106
+ * this changes NOTHING visually. Both rings are white (--kol-accent-primary
107
+ * resolves to --kol-surface-on-primary); the two differ in weight, not hue. */
108
+ --kol-focus-ring-quiet: var(--kol-fg-32);
95
109
  }
96
110
 
97
111
  :root[data-theme="light"] {
@@ -706,6 +706,23 @@
706
706
  padding: 4px 0 4px;
707
707
  }
708
708
 
709
+ /* The rail leaf had NO focus treatment at all (ThreeColumnEditorShell §3,
710
+ * kol-fxr 2026-08-15) — only .is-active and its dot — so it fell through to
711
+ * the browser's default ring, which a user reported as "a weird highlight bug".
712
+ *
713
+ * INSET 1px, matching its sibling .shell-nav-item (kol-components-workshop.css)
714
+ * rather than .kol-btn's `2px / offset 2px`: an offset ring blooms OUTSIDE a
715
+ * dense nav row and collides with the rows above and below. Same element role,
716
+ * same treatment — this closes an inconsistency, it does not mint a policy.
717
+ *
718
+ * NB this is deliberately NOT an answer to FocusRingsInConsumers, which asks
719
+ * whether a consumer can switch the whole focus system off with one token.
720
+ * That is a live ruling; this rule only stops one leaf being the odd one out. */
721
+ .kol-sidenav-link:focus-visible {
722
+ outline: 1px solid var(--kol-focus-ring-quiet);
723
+ outline-offset: -1px;
724
+ }
725
+
709
726
  .kol-sidenav-link.is-active {
710
727
  color: var(--kol-surface-on-primary);
711
728
  }
@@ -787,20 +804,82 @@ a:hover .icon-hover,
787
804
  display: inline-flex;
788
805
  align-items: center;
789
806
  justify-content: center;
790
- padding: 0.5rem;
807
+ /* PINNED SQUARE — the box never moves with glyph size (user law 2026-07-28,
808
+ * the same rule .kol-icon-frame-{sm,md,lg} obeys at 28/32/36). This class
809
+ * derived its size from `padding: 0.5rem` + whatever glyph it was handed, so
810
+ * a 14px icon silently produced a 30px control off the ladder. 32px is md on
811
+ * both the icon-frame squares and the button ladder (26/32/40), and it is
812
+ * exactly what 0.5rem + a 16px glyph already measured — CodeBlock's button
813
+ * does not move. */
814
+ width: 32px;
815
+ height: 32px;
816
+ padding: 0;
817
+ /* rungs below; the base stays md so a class used without one still lands
818
+ * on the ladder rather than collapsing to its content */
791
819
  background: color-mix(in srgb, var(--kol-surface-on-primary) 2%, transparent);
792
820
  border: none;
793
821
  border-radius: var(--kol-radius-sm);
794
- color: color-mix(in srgb, var(--kol-surface-on-primary) 72%, transparent);
822
+ /* ink on the OPAQUE scale (user ruling 2026-08-15): a 72%-transparent glyph
823
+ * over a photograph lets the image through the strokes and reads as mush.
824
+ * oq is a baked grey — same value, nothing bleeds through. */
825
+ color: var(--kol-oq-72);
795
826
  cursor: pointer;
796
827
  transition: background-color var(--kol-transition-base), color var(--kol-transition-base);
797
828
  }
798
829
 
799
830
  .kol-copy-btn:hover {
800
831
  background: color-mix(in srgb, var(--kol-surface-on-primary) 8%, transparent);
832
+ /* FULL ink on hover — no mix, no stop below 100% */
801
833
  color: var(--kol-surface-on-primary);
802
834
  }
803
835
 
836
+ /* the click step, missing since this class was written — hover had a stop,
837
+ * pressed had none, so the button gave no feedback on the way down. */
838
+ .kol-copy-btn:active {
839
+ background: color-mix(in srgb, var(--kol-surface-on-primary) 12%, transparent);
840
+ color: var(--kol-surface-on-primary);
841
+ }
842
+
843
+ /* --grey's ladder runs DOWN, not up (user ruling 2026-08-15). oq mixes ink
844
+ * INTO the surface, so a higher stop is a LIGHTER box — the wrong direction for
845
+ * a control that has to hold its own against a bright image. Rest is oq-12,
846
+ * hover steps darker to oq-08, and the click lands on --kol-surface-primary
847
+ * itself: fully opaque, no mix, the strongest the scale goes. */
848
+ .kol-copy-btn--grey:hover {
849
+ background: var(--kol-oq-08);
850
+ color: var(--kol-surface-on-primary);
851
+ }
852
+ .kol-copy-btn--grey:active {
853
+ background: var(--kol-surface-primary);
854
+ color: var(--kol-surface-on-primary);
855
+ }
856
+
857
+ /* --grey — the same control, over MEDIA instead of over a surface
858
+ * (user ruling 2026-08-15). The base rule and every fg-scale variant are
859
+ * TRANSLUCENT, so they are content-dependent: a light wash vanishes on a light
860
+ * photo, a dark scrim vanishes on a dark one. There is no stop that survives
861
+ * both, because the problem is the transparency, not the value.
862
+ *
863
+ * `oq` is the answer and kol-opaque.css:11 already says so — "use oq when you
864
+ * need an OPAQUE grey (cover layer, CHIP OVER A PHOTO, any fill that must hide
865
+ * what's behind it)". It mixes into the surface instead of into transparent, so
866
+ * the control reads identically whatever the image does.
867
+ *
868
+ * The 12 / 16 / 24 ladder is not new — it is exactly .kol-btn-grey's, and
869
+ * `grey` is already this system's name for an oq fill (kol-btn-grey,
870
+ * kol-dd-trigger--grey, kol-icon-frame-grey). Same name, same stops. */
871
+ /* the same three squares as .kol-icon-frame-{sm,md,lg}. The SOLO glyph ladder
872
+ * (16/20/24) pairs 1:1 with them and ActionButton resolves it — the pairing is
873
+ * never re-typed at a call site. */
874
+ .kol-copy-btn-sm { width: 28px; height: 28px; }
875
+ .kol-copy-btn-md { width: 32px; height: 32px; }
876
+ .kol-copy-btn-lg { width: 36px; height: 36px; }
877
+
878
+ .kol-copy-btn--grey {
879
+ background: var(--kol-oq-12);
880
+ }
881
+
882
+
804
883
  /* ─────────────────────────────────────────────────────────────────────
805
884
  * Figure — atoms/Figure.jsx
806
885
  * Caption'd media shell for long-form prose (label above, aspect-locked
@@ -517,8 +517,14 @@
517
517
  color: var(--kol-fg-emphasis);
518
518
  }
519
519
 
520
+ /* Reads the quiet ring token so the segmented strip is part of the system-wide
521
+ * focus off switch (FocusRingsInConsumers, 2026-08-15). This is the LAST
522
+ * hardcoded focus colour in the theme — with it, `--kol-focus-ring` +
523
+ * `--kol-focus-ring-quiet` set to transparent turns every ring off, which is
524
+ * the whole point of the pair. Offset stays -2px: the cell's own inset, not
525
+ * the rails' -1px. */
520
526
  .kol-seg-cell:focus-visible {
521
- outline: 1px solid var(--kol-fg-emphasis);
527
+ outline: 1px solid var(--kol-focus-ring-quiet);
522
528
  outline-offset: -2px;
523
529
  }
524
530
 
@@ -692,13 +698,51 @@
692
698
  margin-block-end: 0.75rem;
693
699
  }
694
700
 
695
- /* in-frame position only — the button IS the CopyButton atom (.kol-copy-btn
696
- * carries the 32×32 look, promoted there 2026-08-09); offset 0.75rem into
697
- * the 1rem pad. */
701
+ /* ─────────────────────────────────────────────────────────────────────
702
+ * FRAME CONTROL a control that floats in a host frame's corner
703
+ * (user ruling 2026-08-15).
704
+ *
705
+ * Position only. The control IS an existing atom — CopyButton (.kol-copy-btn
706
+ * carries the 32×32 look, promoted 2026-08-09) or IconFrame — and this rule
707
+ * says nothing about how it looks, only where it sits.
708
+ *
709
+ * It exists because the same 12px corner was hand-written three times and
710
+ * shared zero times: `.kol-codeblock-copy` as `0.75rem`, and MediaCard twice
711
+ * inline as `absolute top-3 right-3` (download) and `absolute top-3 left-3`
712
+ * (select). Same value, two spellings, three copies — the 4px-five-ways
713
+ * problem one layer up.
714
+ *
715
+ * NOT `overlay`. That word is ruled below (:704) and means the ONE floating
716
+ * surface; this is in-frame chrome, which is a different thing.
717
+ *
718
+ * The inset spells --kol-spacing-3 (12px) rather than a literal, per the
719
+ * tokens law. Default corner is top-right; the modifiers move it.
720
+ *
721
+ * `.kol-codeblock-copy` is kept as a DEPRECATED ALIAS so existing callers do
722
+ * not break — it is the same rule, not a second one. New hosts use
723
+ * `.kol-frame-control`. */
724
+ .kol-frame-control,
698
725
  .kol-codeblock-copy {
699
726
  position: absolute;
700
- top: 0.75rem;
701
- right: 0.75rem;
727
+ top: var(--kol-spacing-3);
728
+ right: var(--kol-spacing-3);
729
+ }
730
+
731
+ .kol-frame-control--top-left {
732
+ right: auto;
733
+ left: var(--kol-spacing-3);
734
+ }
735
+
736
+ .kol-frame-control--bottom-right {
737
+ top: auto;
738
+ bottom: var(--kol-spacing-3);
739
+ }
740
+
741
+ .kol-frame-control--bottom-left {
742
+ top: auto;
743
+ right: auto;
744
+ bottom: var(--kol-spacing-3);
745
+ left: var(--kol-spacing-3);
702
746
  }
703
747
 
704
748
  /* ─────────────────────────────────────────────────────────────────────
@@ -744,3 +788,31 @@
744
788
  * another product. --kol-shadow-overlay was added for this an hour earlier
745
789
  * and is deleted with it: it solved the wrong problem. */
746
790
  }
791
+
792
+ /* ── Section — .kol-section-* ─────────────────────────────────────────────
793
+ * Between-siblings rhythm for stacked inspector sections (InspectorSectionRhythm,
794
+ * filed from kol-fxr 2026-08-15).
795
+ *
796
+ * Section's own anatomy — the label and the `gap-2` inner stack — stays as
797
+ * Tailwind utilities in Section.jsx. Only the BETWEEN-siblings rule lives here,
798
+ * because `+` cannot be expressed as a utility class: every consumer stacking
799
+ * sections in a rail was therefore forced to invent a hook class and retype
800
+ * this rule. kol-fxr's `.kol-params-section` block is the evidence, and it
801
+ * dies on adopt.
802
+ *
803
+ * The rule is on the ADJACENT PAIR, not on the element, so the first section
804
+ * in a stack never carries a stray top border and no consumer needs
805
+ * `:not(:first-child)`.
806
+ *
807
+ * DENSITY WAS NOT ADDED. The ticket asked for a `density` prop wanting "a
808
+ * tighter inner gap than gap-2" — but the consumer's own override is
809
+ * `gap: 0.5rem`, which IS `gap-2`. The rule it maintains locally is a no-op
810
+ * against Section's shipped default, so there is no second density to name
811
+ * until a consumer produces a value that actually differs. */
812
+ .kol-section--divided + .kol-section--divided {
813
+ border-top: 1px solid var(--kol-fg-08);
814
+ /* 20px below the rule; the 20px above falls out of the rail body's own
815
+ * gap-5, so a symmetric space–divider–space lands without this file
816
+ * knowing anything about the rail. */
817
+ padding-top: var(--kol-spacing-5);
818
+ }
@@ -614,3 +614,16 @@
614
614
  .kol-table-sort svg { opacity: 0.25; transition: opacity var(--kol-transition-fast); }
615
615
  .kol-table-sort:hover svg { opacity: 0.6; }
616
616
  [aria-sort] .kol-table-sort svg { opacity: 1; }
617
+
618
+ /* ─────────────────────────────────────────────────────────────────────
619
+ * ContentCollection — src/organisms/ContentCollection.jsx (2026-08-15)
620
+ * The enter stagger the collection owns; cards never animate themselves.
621
+ * Delay is set per item inline; the curve is the house token. */
622
+ .kol-collection-item {
623
+ animation: kol-collection-in 300ms var(--kol-ease-house) both;
624
+ }
625
+
626
+ @keyframes kol-collection-in {
627
+ from { opacity: 0; transform: translateY(6px); }
628
+ to { opacity: 1; transform: none; }
629
+ }
@@ -364,8 +364,11 @@
364
364
  color: var(--kol-surface-on-primary);
365
365
  }
366
366
 
367
+ /* Reads the quiet ring token, not --kol-fg-32 directly, so this rail is part
368
+ * of the system-wide focus off switch (FocusRingsInConsumers, 2026-08-15).
369
+ * Same value as before — this is a rewiring, not a restyle. */
367
370
  .shell-nav-item:focus-visible {
368
- outline: 1px solid var(--kol-fg-32);
371
+ outline: 1px solid var(--kol-focus-ring-quiet);
369
372
  outline-offset: -1px;
370
373
  }
371
374
 
package/kol-theme.css CHANGED
@@ -87,6 +87,17 @@
87
87
  * indent instead of retyping it. */
88
88
  --kol-pad-rail-row-x: 1.25rem;
89
89
 
90
+ /* CARD padding (2026-08-15, content-card ruling) — flat, stepped by the
91
+ * card's `size` prop, never by breakpoint. 12/16/24. */
92
+ --kol-pad-card-sm: 0.75rem;
93
+ --kol-pad-card-md: 1rem;
94
+ --kol-pad-card-lg: 1.5rem;
95
+
96
+ /* The house curve (2026-08-15) — the easing the cards actually use; was
97
+ * hardcoded in 5 files before this token existed. Curve only, no duration:
98
+ * it pairs with whatever duration the motion needs. */
99
+ --kol-ease-house: cubic-bezier(0.16, 1, 0.3, 1);
100
+
90
101
  /* Content widths (2026-07-28; panel added 2026-07-30) — ONE frame, three
91
102
  * inner caps (chess law). Every page: mx-auto max-w-shell + the one padding
92
103
  * rhythm, content LEFT-ANCHORED inside. Width is a content decision, never a
@@ -815,8 +815,14 @@
815
815
  font-weight: 600;
816
816
  }
817
817
 
818
- /* .kol-sans-display-03 deferredorphan in current consumers. Token defined.
819
- * Add the class when a consumer needs it. */
818
+ /* display-03 shipped 2026-08-15 its consumer arrived: ContentText's `work`
819
+ * row title (the ruled size-only step down from display-02). */
820
+ .kol-sans-display-03 {
821
+ font-family: var(--kol-font-family-sans-narrow);
822
+ font-size: var(--kol-text-display-03);
823
+ line-height: 100%;
824
+ font-weight: 600;
825
+ }
820
826
 
821
827
  /* =============================================================================
822
828
  * DISPLAY TIGHT — the condensed-caps display voice: the Tight cut by NAME
package/kol-utilities.css CHANGED
@@ -123,3 +123,22 @@
123
123
  transition: none;
124
124
  }
125
125
  }
126
+
127
+ /* Placeholder prose — the ONE gate (GatedEmptyState, kol-fxr 2026-08-15).
128
+ *
129
+ * "Select a layer", "No effects yet", "This layer has no parameters" — prose
130
+ * that narrates a surface the user already understands. The ruling is that it
131
+ * is OFF unless asked for, so the suppression is the DEFAULT and the attribute
132
+ * is the opt-IN; a consumer that never wires the toggle simply never sees it.
133
+ *
134
+ * Written as :not() on the root rather than a display:none + revert pair, so
135
+ * the element keeps whatever display it had — flex, grid, inline — instead of
136
+ * being forced back to block on reveal.
137
+ *
138
+ * The attribute is stamped by usePlaceholders() in kol-component. The class is
139
+ * here, in utilities, because it is not EmptyState's chrome: a consumer puts
140
+ * it on its OWN prose and gets the same switch. That is the whole point — one
141
+ * concept, one gate, not a per-component prop each app re-invents. */
142
+ :root:not([data-kol-placeholders]) .kol-placeholder {
143
+ display: none;
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-theme",
3
- "version": "0.42.2",
3
+ "version": "0.43.0",
4
4
  "description": "KOL (Kolkrabbi) design-system tokens + base CSS — brand-neutral. The canonical token/cascade layer every other KOL package and consumer builds on.",
5
5
  "license": "MIT",
6
6
  "type": "module",