@birdycom/omniaui 0.12.6 → 0.13.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.
@@ -1,4 +1,4 @@
1
- /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.12.6. Do not edit. */
1
+ /* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss 0.13.0. Do not edit. */
2
2
  /* Semantic theme tokens: every color is light-dark() of an Omnia palette token (oc-colors.css). */
3
3
  /* Same source as the desktop export: npx omniacss tokens --format json|dtcg|python */
4
4
  :root {
@@ -14,6 +14,7 @@
14
14
  --oc-foreground: light-dark(var(--color-duskblue-60), var(--color-frostblue-10));
15
15
  --oc-muted: light-dark(var(--color-frostblue-60), var(--color-shallowblue-50));
16
16
  --oc-muted-foreground: light-dark(var(--color-stormblue-40), var(--color-mistblue-70));
17
+ --oc-subtle-foreground: light-dark(var(--color-stormblue-10), var(--color-mistblue-95));
17
18
  --oc-card: light-dark(var(--color-white-50), var(--color-shallowblue-70));
18
19
  --oc-popover: light-dark(var(--color-white-50), var(--color-shallowblue-60));
19
20
  --oc-popover-foreground: var(--oc-foreground);
@@ -58,6 +59,7 @@
58
59
  --oc-transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);
59
60
  --oc-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Cantarell, "Helvetica Neue", sans-serif;
60
61
  --oc-font-mono: "Cascadia Code", "JetBrains Mono", Consolas, ui-monospace, monospace;
62
+ --oc-font-display: system-ui, -apple-system, "Segoe UI", Roboto, Cantarell, "Helvetica Neue", sans-serif;
61
63
 
62
64
  /* Shadows (x y blur spread; color + alpha per mode in --oc-shadow-<name>-color) */
63
65
  --oc-shadow-sm-color: light-dark(oklch(from var(--color-stormblue-50) l c h / 0.08), oklch(from var(--color-black-50) l c h / 0.4));
@@ -346,7 +348,8 @@ flex-direction: column;
346
348
  justify-content: center;
347
349
  gap: 8px;
348
350
  font-family: inherit;
349
- font-weight: 500;
351
+ /* --oc-btn-weight: a theme can make every button bolder (600). */
352
+ font-weight: var(--oc-btn-weight, 500);
350
353
  font-size: 0.875rem;
351
354
  line-height: 1.25rem;
352
355
  padding: 0.5rem 1rem;
@@ -481,8 +484,8 @@ flex-direction: column;
481
484
  }
482
485
 
483
486
  .oc-btn--lg {
484
- font-size: 1rem;
485
- padding: 0.625rem 1.5rem;
487
+ font-size: var(--oc-btn-lg-size, 1rem);
488
+ padding: var(--oc-btn-lg-padding, 0.625rem 1.5rem);
486
489
  border-radius: calc(var(--oc-radius) + 2px);
487
490
  }
488
491
 
@@ -710,8 +713,10 @@ flex-direction: column;
710
713
  }
711
714
 
712
715
  .oc-field-error {
716
+ margin: 0;
713
717
  font-size: 0.8rem;
714
- color: var(--oc-destructive);
718
+ /* In dark mode lifted to at least OKLCH L 0.78, so the message stays readable on a dark card. */
719
+ color: light-dark(var(--oc-destructive), oklch(from var(--oc-destructive) max(l, 0.78) c h));
715
720
  }
716
721
 
717
722
  /* Field group — wraps multiple fields with consistent spacing */
@@ -724,17 +729,23 @@ flex-direction: column;
724
729
  grid-template-columns: 1fr 1fr;
725
730
  }
726
731
 
727
- /* Text input */
732
+ /* Narrow screens: the fields of a row stack. */
733
+ @media (max-width: 560px) {
734
+ .oc-field-group--row { grid-template-columns: 1fr; }
735
+ }
736
+
737
+ /* Text input. A theme or a form variant (.oc-form--filled) can set the field tokens: --oc-input-bg, --oc-input-border,
738
+ --oc-input-padding, --oc-input-radius, --oc-input-size, --oc-ring-width and --oc-ring-alpha (focus ring). */
728
739
  .oc-input {
729
740
  display: block;
730
741
  width: 100%;
731
742
  font-family: inherit;
732
- font-size: 0.875rem;
743
+ font-size: var(--oc-input-size, 0.875rem);
733
744
  line-height: 1.5;
734
- padding: 0.5rem 0.75rem;
735
- border: 1px solid var(--oc-input);
736
- border-radius: var(--oc-radius);
737
- background: transparent;
745
+ padding: var(--oc-input-padding, 0.5rem 0.75rem);
746
+ border: 1px solid var(--oc-input-border, var(--oc-input));
747
+ border-radius: var(--oc-input-radius, var(--oc-radius));
748
+ background: var(--oc-input-bg, transparent);
738
749
  color: var(--oc-foreground);
739
750
  transition: border-color var(--oc-transition-fast), box-shadow var(--oc-transition-fast);
740
751
  outline: none;
@@ -746,7 +757,7 @@ flex-direction: column;
746
757
 
747
758
  .oc-input:focus {
748
759
  border-color: var(--oc-ring);
749
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--oc-ring) 25%, transparent);
760
+ box-shadow: 0 0 0 var(--oc-ring-width, 2px) color-mix(in srgb, var(--oc-ring) var(--oc-ring-alpha, 25%), transparent);
750
761
  }
751
762
 
752
763
  .oc-input:disabled {
@@ -762,12 +773,13 @@ flex-direction: column;
762
773
 
763
774
  .oc-input[aria-invalid="true"]:focus,
764
775
  .oc-input--error:focus {
765
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--oc-destructive) 25%, transparent);
776
+ box-shadow: 0 0 0 var(--oc-ring-width, 2px) color-mix(in srgb, var(--oc-destructive) var(--oc-ring-alpha, 25%), transparent);
766
777
  }
767
778
 
768
779
  /* Textarea — same base as input */
769
- textarea.oc-input {
770
- min-height: 80px;
780
+ textarea.oc-input,
781
+ .oc-textarea {
782
+ min-height: var(--oc-textarea-min, 80px);
771
783
  resize: vertical;
772
784
  }
773
785
 
@@ -811,12 +823,13 @@ textarea.oc-input {
811
823
  display: block;
812
824
  width: 100%;
813
825
  font-family: inherit;
814
- font-size: 0.875rem;
826
+ font-size: var(--oc-input-size, 0.875rem);
815
827
  line-height: 1.5;
816
- padding: 0.5rem 2rem 0.5rem 0.75rem;
817
- border: 1px solid var(--oc-input);
818
- border-radius: var(--oc-radius);
819
- background: transparent;
828
+ padding: var(--oc-input-padding, 0.5rem 0.75rem);
829
+ padding-inline-end: 2rem;
830
+ border: 1px solid var(--oc-input-border, var(--oc-input));
831
+ border-radius: var(--oc-input-radius, var(--oc-radius));
832
+ background-color: var(--oc-input-bg, transparent);
820
833
  color: var(--oc-foreground);
821
834
  appearance: none;
822
835
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2371717a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
@@ -830,8 +843,9 @@ textarea.oc-input {
830
843
 
831
844
  .oc-select:focus {
832
845
  border-color: var(--oc-ring);
833
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--oc-ring) 25%, transparent);
846
+ box-shadow: 0 0 0 var(--oc-ring-width, 2px) color-mix(in srgb, var(--oc-ring) var(--oc-ring-alpha, 25%), transparent);
834
847
  }
848
+ .oc-select[aria-invalid="true"] { border-color: var(--oc-destructive); }
835
849
 
836
850
  /* Select option dark theme — prevents white background in dropdowns */
837
851
  .oc-select option {
@@ -1599,7 +1613,7 @@ textarea.oc-input {
1599
1613
  border: none;
1600
1614
  height: 1px;
1601
1615
  background: var(--oc-border);
1602
- margin: 16px 0;
1616
+ margin: var(--oc-separator-space, 16px) 0;
1603
1617
  }
1604
1618
 
1605
1619
  .oc-separator--vertical {
@@ -3789,7 +3803,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3789
3803
  /* border-box: the gradient also runs under the transparent 1px border of .oc-btn; from the padding box it would
3790
3804
  repeat in that border and show a thin stripe of the other end's color on both sides. */
3791
3805
  background: linear-gradient(100deg, var(--oc-accent-from), var(--oc-accent-to)) border-box;
3792
- color: var(--color-white-50);
3806
+ /* --oc-btn-gradient-ink: dark text on a light accent (set it with the accent). */
3807
+ color: var(--oc-btn-gradient-ink, var(--color-white-50));
3793
3808
  box-shadow: 0 10px 30px -10px var(--oc-accent-to);
3794
3809
  transition: transform 0.2s var(--oc-ease-out), box-shadow 0.2s;
3795
3810
  }
@@ -3816,7 +3831,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3816
3831
 
3817
3832
  /* Backdrop: slowly drifting color blobs + fine grid behind the page.
3818
3833
  <div class="oc-backdrop" aria-hidden="true"><i class="oc-backdrop__blob"></i><i class="oc-backdrop__grid"></i></div>
3819
- .oc-backdrop--local keeps it inside a positioned section instead of the viewport. */
3834
+ .oc-backdrop--local keeps it inside a positioned section instead of the viewport.
3835
+ A theme sets the colors: --oc-backdrop-1, -2, -3 (the blobs), --oc-backdrop-3-opacity and --oc-backdrop-grid (the lines). */
3820
3836
  .oc-backdrop {
3821
3837
  position: fixed;
3822
3838
  inset: 0;
@@ -3837,24 +3853,27 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3837
3853
  opacity: var(--oc-backdrop-opacity, 0.35);
3838
3854
  animation: oc-drift 24s ease-in-out infinite alternate;
3839
3855
  }
3840
- .oc-backdrop::before { background: var(--color-blue-40); inset-block-start: -18rem; inset-inline-start: -12rem; }
3841
- .oc-backdrop::after { background: var(--color-purple-30); inset-block-start: 10rem; inset-inline-end: -16rem; animation-delay: -8s; }
3842
- .oc-backdrop > .oc-backdrop__blob { background: var(--color-teal-40); inset-block-end: -20rem; inset-inline-start: 30%; animation-delay: -16s; opacity: calc(var(--oc-backdrop-opacity, 0.35) * 0.6); }
3856
+ .oc-backdrop::before { background: var(--oc-backdrop-1, var(--color-blue-40)); inset-block-start: -18rem; inset-inline-start: -12rem; }
3857
+ .oc-backdrop::after { background: var(--oc-backdrop-2, var(--color-purple-30)); inset-block-start: 10rem; inset-inline-end: -16rem; animation-delay: -8s; }
3858
+ .oc-backdrop > .oc-backdrop__blob { background: var(--oc-backdrop-3, var(--color-teal-40)); inset-block-end: -20rem; inset-inline-start: 30%; animation-delay: -16s; opacity: var(--oc-backdrop-3-opacity, calc(var(--oc-backdrop-opacity, 0.35) * 0.6)); }
3843
3859
  .oc-backdrop > .oc-backdrop__grid {
3844
3860
  position: absolute;
3845
3861
  inset: 0;
3846
3862
  background-image:
3847
- linear-gradient(oklch(from var(--oc-foreground) l c h / 0.05) 1px, transparent 1px),
3848
- linear-gradient(90deg, oklch(from var(--oc-foreground) l c h / 0.05) 1px, transparent 1px);
3863
+ linear-gradient(var(--oc-backdrop-grid, oklch(from var(--oc-foreground) l c h / 0.05)) 1px, transparent 1px),
3864
+ linear-gradient(90deg, var(--oc-backdrop-grid, oklch(from var(--oc-foreground) l c h / 0.05)) 1px, transparent 1px);
3849
3865
  background-size: 64px 64px;
3850
3866
  mask-image: radial-gradient(ellipse at 50% 0%, black 20%, transparent 70%);
3851
3867
  }
3852
3868
  @keyframes oc-drift { to { transform: translate(6rem, 4rem) scale(1.15); } }
3853
3869
 
3854
3870
  /* Container, section spacing and display headings */
3855
- .oc-container { inline-size: min(100% - 2.5rem, var(--oc-container, 1200px)); margin-inline: auto; }
3871
+ /* --oc-container is the width, --oc-container-gutter the room on each side on a narrower screen (1.25rem). */
3872
+ .oc-container { inline-size: min(100% - 2 * var(--oc-container-gutter, 1.25rem), var(--oc-container, 1200px)); margin-inline: auto; }
3856
3873
  .oc-section-pad { padding-block: clamp(4rem, 9vw, 7rem); }
3857
3874
  .oc-section-pad--tight { padding-block: clamp(2rem, 5vw, 3.5rem); }
3875
+ /* The hero of a home page: a little less room on top and below than a section. */
3876
+ .oc-section-pad--hero { padding-block: clamp(3.5rem, 9vw, 7rem) clamp(3rem, 6vw, 5rem); }
3858
3877
  .oc-display {
3859
3878
  margin: 0 0 1.5rem;
3860
3879
  font-size: clamp(2.6rem, 5.6vw, 4.6rem);
@@ -3864,8 +3883,28 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3864
3883
  text-wrap: balance;
3865
3884
  }
3866
3885
  .oc-display--md { font-size: clamp(1.9rem, 4vw, 3rem); }
3886
+ .oc-display--sm { font-size: clamp(1.5rem, 3vw, 2.1rem); }
3867
3887
  .oc-lead { margin: 0 0 1rem; color: var(--oc-muted-foreground); font-size: 1.15rem; line-height: 1.65; max-inline-size: 42rem; text-wrap: pretty; }
3868
- .oc-split { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr)); gap: 3rem; align-items: center; }
3888
+ /* Larger and narrower, for a home hero. */
3889
+ .oc-lead--lg { font-size: 1.2rem; max-inline-size: 36rem; }
3890
+ .oc-split { display: grid; grid-template-columns: var(--oc-split-cols, repeat(auto-fit, minmax(min(100%, 24rem), 1fr))); gap: var(--oc-split-gap, 3rem); align-items: center; }
3891
+ /* Ratio presets (one column up to 960px): --lead makes the text column wider, --media the image column.
3892
+ Your own ratio: set --oc-split-cols in a class (and to 1fr in a media query for small screens). */
3893
+ .oc-split--lead { --oc-split-cols: 1.25fr 1fr; }
3894
+ .oc-split--media { --oc-split-cols: 1fr 1.15fr; }
3895
+ .oc-split__media { display: grid; place-items: center; }
3896
+ /* Columns aligned at the top (a fact list next to a long text). */
3897
+ .oc-split--top { align-items: start; }
3898
+ /* --oc-split-cols-wide: your own ratio that only applies above 960px, so the split still stacks on a phone. */
3899
+ @media (min-width: 961px) {
3900
+ .oc-split { grid-template-columns: var(--oc-split-cols-wide, var(--oc-split-cols, repeat(auto-fit, minmax(min(100%, 24rem), 1fr)))); }
3901
+ }
3902
+ @media (max-width: 960px) {
3903
+ .oc-split--lead, .oc-split--media { --oc-split-cols: 1fr; }
3904
+ /* The image (a product mark, a screenshot) comes first on a small screen, aligned at the start, and an xl tile shrinks. */
3905
+ .oc-split__media { order: -1; justify-items: start; }
3906
+ .oc-split__media .oc-icon-tile--xl { --oc-tile-size: 7rem; }
3907
+ }
3869
3908
 
3870
3909
  /* Code window: editor frame with traffic lights and a file name. It follows the page theme: a light frame and light
3871
3910
  syntax colors in light mode, the Omnia Dark editor colors in dark mode. --dark keeps it dark on a light page (the
@@ -3953,7 +3992,12 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3953
3992
  @keyframes oc-float { 50% { translate: 0 -12px; } }
3954
3993
 
3955
3994
  /* Product / feature card with hover lift and accent glow */
3956
- .oc-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--oc-card-min, 17rem)), 1fr)); gap: 1.25rem; }
3995
+ .oc-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--oc-card-min, 17rem)), 1fr)); gap: var(--oc-card-gap, 1.25rem); }
3996
+ /* Wide cards (projects, case studies): from 32rem, three in a row on a wide screen. */
3997
+ .oc-card-grid--wide { --oc-card-min: 32rem; --oc-card-gap: 1.5rem; }
3998
+ /* --fit: the cards stretch over the full width when there are fewer than fit in a row (auto-fit instead of auto-fill). */
3999
+ .oc-card-grid--fit { grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--oc-card-min, 17rem)), 1fr)); }
4000
+ @media (min-width: 1280px) { .oc-card-grid--wide { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
3957
4001
  .oc-product-card {
3958
4002
  position: relative;
3959
4003
  display: flex;
@@ -3967,7 +4011,7 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3967
4011
  var(--oc-surface);
3968
4012
  color: var(--oc-foreground);
3969
4013
  text-decoration: none;
3970
- transition: transform 0.35s var(--oc-ease-out), border-color 0.35s, --oc-glow 0.35s;
4014
+ transition: translate 0.35s var(--oc-ease-out), border-color 0.35s, --oc-glow 0.35s;
3971
4015
  }
3972
4016
  @property --oc-glow {
3973
4017
  syntax: "<number>";
@@ -3976,7 +4020,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
3976
4020
  }
3977
4021
  .oc-product-card:hover {
3978
4022
  --oc-glow: 0.22;
3979
- transform: translateY(-6px);
4023
+ /* The lift uses translate; a fly-in (.oc-reveal--fly) animates the transform, so both keep working. */
4024
+ translate: 0 calc(-1 * var(--oc-hover-lift, 6px));
3980
4025
  border-color: oklch(from var(--oc-accent-from) l c h / 0.55);
3981
4026
  }
3982
4027
  .oc-product-card__top { display: flex; justify-content: space-between; align-items: flex-start; margin-block-end: 0.75rem; }
@@ -4032,9 +4077,17 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4032
4077
  }
4033
4078
  .oc-stat-strip__item { padding: 2rem 1.5rem; border-inline-start: 1px solid var(--oc-border); }
4034
4079
  .oc-stat-strip__item:first-child { border-inline-start: 0; }
4035
- .oc-stat-strip__value { font-size: clamp(2.2rem, 4vw, 3rem); font-weight: 700; line-height: 1.1; }
4080
+ .oc-stat-strip__value { font-family: var(--oc-font-display, inherit); font-size: clamp(2.2rem, 4vw, 3rem); font-weight: 700; line-height: 1.1; }
4036
4081
  .oc-stat-strip__suffix { color: var(--oc-brand-1); }
4037
4082
  .oc-stat-strip__label { display: block; color: var(--oc-muted-foreground); font-size: 0.92rem; }
4083
+ /* Plain: no lines and no background of its own, for a strip inside an .oc-band (full width) or a card. */
4084
+ .oc-stat-strip--plain { border-block: 0; background: none; }
4085
+ /* Four figures on a narrower screen: two rows of two instead of three and one. */
4086
+ @media (max-width: 960px) {
4087
+ .oc-stat-strip:has(> .oc-stat-strip__item:nth-child(4):last-child) { grid-template-columns: 1fr 1fr; }
4088
+ .oc-stat-strip:has(> .oc-stat-strip__item:nth-child(4):last-child) > .oc-stat-strip__item:nth-child(3) { border-inline-start: 0; }
4089
+ .oc-stat-strip:has(> .oc-stat-strip__item:nth-child(4):last-child) > .oc-stat-strip__item:nth-child(n + 3) { border-block-start: 1px solid var(--oc-border); }
4090
+ }
4038
4091
 
4039
4092
  /* Spotlight: large highlighted panel with copy + module list */
4040
4093
  .oc-spotlight {
@@ -4052,8 +4105,12 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4052
4105
  }
4053
4106
  .oc-spotlight__title { display: flex; align-items: center; gap: 1rem; margin: 1.25rem 0 0.5rem; font-size: clamp(2.2rem, 5vw, 3.5rem); line-height: 1.1; }
4054
4107
  .oc-spotlight__tagline { color: light-dark(var(--oc-accent-from), oklch(from var(--oc-accent-from) max(l, 0.72) c h)); font-size: 1.2rem; font-weight: 600; }
4108
+ /* --oc-spotlight-cols: your own ratio above 960px (copy | modules); on a smaller screen the spotlight stacks. */
4109
+ @media (min-width: 961px) {
4110
+ .oc-spotlight { grid-template-columns: var(--oc-spotlight-cols, repeat(auto-fit, minmax(min(100%, 22rem), 1fr))); }
4111
+ }
4055
4112
  .oc-spotlight__modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 0.75rem; margin: 0; padding: 0; list-style: none; }
4056
- .oc-spotlight__modules > li { display: flex; align-items: center; gap: 0.75rem; padding: 0.9rem; border: 1px solid var(--oc-border); border-radius: var(--oc-radius-md); background: var(--oc-surface); font-size: 0.95rem; }
4113
+ .oc-spotlight__modules > li { display: flex; align-items: center; gap: 0.75rem; padding: 0.9rem; border: 1px solid var(--oc-border); border-radius: var(--oc-radius-md); background: var(--oc-spotlight-module-bg, var(--oc-surface)); font-size: 0.95rem; }
4057
4114
 
4058
4115
  /* Steps: numbered process with a gradient tick (numbers come from a CSS counter) */
4059
4116
  .oc-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: 1.25rem; margin: 0; padding: 0; list-style: none; counter-reset: oc-step; }
@@ -4086,6 +4143,9 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4086
4143
  /* Tag list (tech stack) and check list */
4087
4144
  .oc-tag-list { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 0; padding: 0; list-style: none; }
4088
4145
  .oc-tag-list > li { padding: 0.55rem 1.1rem; border: 1px solid oklch(from var(--oc-accent-from) l c h / 0.35); border-radius: 999px; background: oklch(from var(--oc-accent-from) l c h / 0.1); font-family: var(--oc-font-mono); font-size: 0.88rem; }
4146
+ /* Neutral chips (no accent color) and large chips in the display font, e.g. a strip of brand names. */
4147
+ .oc-tag-list--neutral > li { border-color: var(--oc-border); background: var(--oc-surface); color: var(--oc-foreground); }
4148
+ .oc-tag-list--lg > li { padding: 0.45rem 1rem; font-family: var(--oc-font-display, inherit); font-size: 1rem; font-weight: 700; letter-spacing: 0.02em; }
4089
4149
  .oc-check-list { display: grid; gap: 0.5rem; margin: 1.1rem 0 0; padding: 0; list-style: none; font-size: 0.93rem; }
4090
4150
  .oc-check-list > li { display: flex; gap: 0.5rem; align-items: baseline; }
4091
4151
  .oc-check-list > li::before { content: "✓"; color: var(--oc-brand-1); font-weight: 700; }
@@ -4120,6 +4180,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4120
4180
  .oc-site-header__nav a:not(.oc-btn):hover,
4121
4181
  .oc-site-header__nav a[aria-current="page"] { color: var(--oc-foreground); }
4122
4182
  .oc-site-header__toggle { display: none; padding: 0.5rem 0.7rem; border: 1px solid var(--oc-border); border-radius: var(--oc-radius-md); background: var(--oc-surface); color: var(--oc-foreground); cursor: pointer; }
4183
+ /* The icon in the menu button is a block, so the button is as tall as a line of text (no inline gap under it). */
4184
+ .oc-site-header__toggle :is(svg, img, iconify-icon, .oc-icon) { display: block; }
4123
4185
  @media (min-width: 861px) {
4124
4186
  /* Desktop: the popover nav is always visible in the header bar. */
4125
4187
  .oc-site-header__nav[popover] { display: flex; }
@@ -4144,6 +4206,8 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4144
4206
  backdrop-filter: blur(16px);
4145
4207
  }
4146
4208
  .oc-site-header__nav[popover] a:not(.oc-btn) { padding: 0.75rem 0; font-size: 1.1rem; }
4209
+ /* The call to action (.oc-site-header__cta, a button) gets room above it in the open menu. */
4210
+ .oc-site-header__cta { margin-block-start: 0.75rem; }
4147
4211
  }
4148
4212
 
4149
4213
  /* Site footer: brand column + link columns + bottom bar */
@@ -4151,21 +4215,33 @@ details.oc-collapsible[open]::details-content { block-size: auto; }
4151
4215
  margin-block-start: 4rem;
4152
4216
  padding-block-start: 4rem;
4153
4217
  border-block-start: 1px solid var(--oc-border);
4154
- background: linear-gradient(to bottom, transparent, var(--oc-surface-strong));
4218
+ background: var(--oc-footer-bg, linear-gradient(to bottom, transparent, var(--oc-surface-strong)));
4155
4219
  color: var(--oc-muted-foreground);
4156
4220
  font-size: 0.95rem;
4221
+ container-type: inline-size;
4157
4222
  }
4158
- .oc-site-footer__grid { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 2.5rem; }
4223
+ /* Columns: --oc-footer-cols (about + 3 link columns). Footer narrower than 66rem (about a 1060px screen):
4224
+ --oc-footer-cols-md columns (3) with the about column over the full width; narrower than 54rem: two columns.
4225
+ Container queries on the footer, so a footer in a narrow column or a preview follows its own width. */
4226
+ .oc-site-footer__grid { display: grid; grid-template-columns: var(--oc-footer-cols, 1.6fr repeat(3, 1fr)); gap: 2.5rem; }
4159
4227
  .oc-site-footer__about p { margin-block: 1rem 0; max-inline-size: 22rem; }
4160
4228
  .oc-site-footer__title { margin: 0 0 1rem; color: var(--oc-foreground); font-size: 0.95rem; font-weight: 700; }
4161
4229
  .oc-site-footer__list { display: flex; flex-direction: column; gap: 0.5rem; margin: 0; padding: 0; list-style: none; font-style: normal; }
4162
4230
  .oc-site-footer a { color: inherit; text-decoration: none; }
4163
4231
  .oc-site-footer a:hover { color: var(--oc-foreground); }
4164
- .oc-site-footer__bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; margin-block-start: 3rem; padding-block: 1.5rem; border-block-start: 1px solid var(--oc-border); font-size: 0.85rem; }
4165
- @media (max-width: 860px) {
4166
- .oc-site-footer__grid { grid-template-columns: 1fr 1fr; }
4232
+ .oc-site-footer__bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; margin-block-start: 3rem; padding-block: 1.5rem; border-block-start: 1px solid var(--oc-border); color: var(--oc-footer-bottom-color, inherit); font-size: 0.85rem; }
4233
+ /* A second title further down in a column (e.g. "Tools" under the projects). */
4234
+ .oc-site-footer__subtitle { margin-block-start: 1.75rem; }
4235
+ /* Legal links in the bottom bar (Terms · Privacy · Cookies) and the build number after them. */
4236
+ .oc-site-footer__legal { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; margin: 0; padding: 0; list-style: none; }
4237
+ .oc-site-footer__build { opacity: 0.7; font-variant-numeric: tabular-nums; }
4238
+ @container (max-width: 66rem) {
4239
+ .oc-site-footer__grid { grid-template-columns: repeat(var(--oc-footer-cols-md, 3), minmax(0, 1fr)); }
4167
4240
  .oc-site-footer__about { grid-column: 1 / -1; }
4168
4241
  }
4242
+ @container (max-width: 54rem) {
4243
+ .oc-site-footer__grid { grid-template-columns: 1fr 1fr; }
4244
+ }
4169
4245
 
4170
4246
  /* Reveal on scroll: elements slide in when they enter the viewport (scroll-driven, no JS; visible without support) */
4171
4247
  @supports (animation-timeline: view()) {
@@ -6320,6 +6396,47 @@ input.oc-input[type="month"] { inline-size: auto; min-inline-size: 11rem; }
6320
6396
  .oc-page-header--sm { padding-block: 0.5rem 0.75rem; }
6321
6397
  .oc-page-header--sm .oc-page-header-title { font-size: 1.125rem; }
6322
6398
 
6399
+ /* parts/app-result.css */
6400
+ /* File: packages/ui/src/parts/app-result.css */
6401
+ /* Map: result -> icon tile -> title -> text -> actions */
6402
+ /* The outcome of an action in the place where the form was: "Thank you, your message was sent" with an icon tile, a
6403
+ short text and a button ("Send another message"). Give it role="status" and tabindex="-1" and move the focus to it.
6404
+ --center centers it; the tile takes its color from an .oc-accent--* preset. */
6405
+
6406
+ .oc-result {
6407
+ display: grid;
6408
+ justify-items: start;
6409
+ gap: 0.75rem;
6410
+ }
6411
+ .oc-result:focus { outline: none; }
6412
+ .oc-result[hidden] { display: none; }
6413
+ .oc-result > .oc-icon-tile { margin-block-end: 0.35rem; }
6414
+ .oc-result__title {
6415
+ margin: 0;
6416
+ font-size: 1.2rem;
6417
+ line-height: 1.25;
6418
+ }
6419
+ .oc-result__text {
6420
+ margin: 0;
6421
+ color: var(--oc-muted-foreground);
6422
+ }
6423
+ .oc-result__text a {
6424
+ color: var(--oc-primary);
6425
+ text-decoration: underline;
6426
+ text-underline-offset: 3px;
6427
+ }
6428
+ .oc-result__actions {
6429
+ display: flex;
6430
+ flex-wrap: wrap;
6431
+ gap: 0.75rem;
6432
+ margin-block-start: 0.5rem;
6433
+ }
6434
+ .oc-result--center {
6435
+ justify-items: center;
6436
+ text-align: center;
6437
+ }
6438
+ .oc-result--center .oc-result__actions { justify-content: center; }
6439
+
6323
6440
  /* parts/app-severity-card.css */
6324
6441
  /* File: packages/ui/src/parts/app-severity-card.css | Map: card (details) -> head (summary) -> icon + title + tags -> level badge + chevron -> body + actions -> severity modifiers -> resolved state -> reduced motion */
6325
6442
  /* Severity card: risk-colored expandable card (security finding, incident, audit item).
@@ -8259,6 +8376,84 @@ td[data-numeric] > .oc-price .oc-price__main, .oc-price--end .oc-price__main { j
8259
8376
  .oc-form-section__summary::after { transition: none; }
8260
8377
  }
8261
8378
 
8379
+ /* parts/forms-form.css */
8380
+ /* File: packages/ui/src/parts/forms-form.css */
8381
+ /* Map: form grid -> label hint -> actions + note -> busy -> filled variant */
8382
+ /* A form as one grid of fields with one gap, an "(optional)" hint in a label, a row with the submit button and a short
8383
+ note, the busy state while it sends (aria-busy on the form), and .oc-form--filled: fields with a fill and a stronger
8384
+ border, for a form on a card, a photo or a dark band. All through the field tokens of .oc-input. */
8385
+
8386
+ .oc-form {
8387
+ display: grid;
8388
+ gap: var(--oc-form-gap, 1.1rem);
8389
+ }
8390
+ .oc-form .oc-field-group { gap: var(--oc-form-gap, 1.1rem); }
8391
+ .oc-form .oc-field {
8392
+ gap: 0.4rem;
8393
+ margin-bottom: 0;
8394
+ }
8395
+
8396
+ /* <label class="oc-field-label">Phone <span class="oc-field-label__hint">(optional)</span></label> */
8397
+ .oc-field-label__hint {
8398
+ color: var(--oc-muted-foreground);
8399
+ font-weight: 400;
8400
+ }
8401
+
8402
+ /* Submit button with a short note next to it (wraps under it on a narrow form). */
8403
+ .oc-form-actions {
8404
+ display: flex;
8405
+ flex-wrap: wrap;
8406
+ align-items: center;
8407
+ gap: 1rem 1.5rem;
8408
+ margin-block-start: 0.4rem;
8409
+ }
8410
+ .oc-form-note {
8411
+ flex: 1 1 16rem;
8412
+ margin: 0;
8413
+ color: var(--oc-muted-foreground);
8414
+ font-size: 0.85rem;
8415
+ }
8416
+ .oc-form-note a {
8417
+ color: var(--oc-primary);
8418
+ text-decoration: underline;
8419
+ text-underline-offset: 3px;
8420
+ }
8421
+ .oc-form-note a:hover { color: var(--oc-foreground); }
8422
+
8423
+ /* While the form sends (form aria-busy="true"): the submit button dims and shows a progress cursor. */
8424
+ .oc-form[aria-busy="true"] :is([type="submit"], button:not([type])) {
8425
+ opacity: 0.7;
8426
+ cursor: progress;
8427
+ }
8428
+
8429
+ /* Filled fields: a fill (dark in dark mode), the strong border, more room and a wider focus ring. */
8430
+ .oc-form--filled {
8431
+ --oc-input-bg: light-dark(oklch(from var(--oc-foreground) l c h / 0.03), oklch(0 0 0 / 0.25));
8432
+ --oc-input-border: var(--oc-border-strong);
8433
+ --oc-input-padding: 0.75rem 0.9rem;
8434
+ --oc-input-radius: var(--oc-radius-md, 0.75rem);
8435
+ --oc-input-size: 1rem;
8436
+ --oc-ring-width: 3px;
8437
+ --oc-textarea-min: 9rem;
8438
+ }
8439
+ .oc-form--filled .oc-field-label {
8440
+ font-size: 0.9rem;
8441
+ font-weight: 600;
8442
+ line-height: 1.4;
8443
+ }
8444
+ .oc-form--filled .oc-field-error { font-size: 0.85rem; }
8445
+ .oc-form--filled .oc-select option { background: var(--oc-card); }
8446
+
8447
+ /* Honeypot: a field for bots, off screen for people. Not display:none (some bots skip hidden fields). Give the wrapper
8448
+ aria-hidden="true" and the input tabindex="-1" autocomplete="off"; the server ignores a form where it is filled. */
8449
+ .oc-honeypot {
8450
+ position: absolute;
8451
+ inset-inline-start: -10000px;
8452
+ inline-size: 1px;
8453
+ block-size: 1px;
8454
+ overflow: hidden;
8455
+ }
8456
+
8262
8457
  /* parts/forms-language-select.css */
8263
8458
  /* File: packages/ui/src/parts/forms-language-select.css
8264
8459
  Map: wrapper (icon + native select) -> icon -> select control -> chevron -> states -> --ghost / --currency
@@ -10226,6 +10421,10 @@ a.oc-attachment-image:hover > :is(img, .oc-media-art) { filter: brightness(1.05)
10226
10421
  .oc-lightbox__nav { grid-row: 1; justify-self: center; }
10227
10422
  .oc-lightbox__nav--prev { grid-column: 1; }
10228
10423
  .oc-lightbox__nav--next { grid-column: 3; }
10424
+ .oc-lightbox__close :is(svg, iconify-icon, .oc-icon) { inline-size: 1.1rem; block-size: 1.1rem; font-size: 1.1rem; }
10425
+ /* A <picture> (animated WebP with a still image for "reduce motion") gets the frame; the image fits in it. */
10426
+ .oc-lightbox__media > picture { display: grid; overflow: hidden; }
10427
+ .oc-lightbox__media > picture > img { max-inline-size: 100%; max-block-size: calc(100dvh - 9rem); object-fit: contain; }
10229
10428
 
10230
10429
  /* ---- Caption with the "2 / 5" counter ---- */
10231
10430
  .oc-lightbox__caption {
@@ -11110,6 +11309,284 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11110
11309
  .oc-footerbar--dark :is(a, .oc-footerbar-links a) { color: oklch(from var(--color-white-50) l c h / 0.85); }
11111
11310
  .oc-footerbar--dark .oc-footerbar-links a:hover { color: var(--color-white-50); }
11112
11311
 
11312
+ /* parts/site-base.css */
11313
+ /* File: packages/ui/src/parts/site-base.css */
11314
+ /* Map: skip link -> icon */
11315
+ /* Two small site helpers: a skip link for keyboard users (hidden above the page until it gets the focus) and a basic
11316
+ inline icon (svg or iconify-icon) that is as large as the text around it. */
11317
+
11318
+ /* <a class="oc-skip-link" href="#main">Skip to content</a> as the first element in <body>. */
11319
+ .oc-skip-link {
11320
+ position: absolute;
11321
+ inset-inline-start: 1rem;
11322
+ inset-block-start: 1rem;
11323
+ /* Fully above the edge, whatever its height; drops in on focus. */
11324
+ translate: 0 calc(-100% - 2rem);
11325
+ z-index: 100;
11326
+ padding: 0.6rem 1rem;
11327
+ border-radius: var(--oc-radius-md, 0.75rem);
11328
+ background: var(--oc-foreground);
11329
+ color: var(--oc-background);
11330
+ font-weight: 600;
11331
+ text-decoration: none;
11332
+ transition: translate var(--oc-transition-fast);
11333
+ }
11334
+ .oc-skip-link:focus { translate: none; }
11335
+
11336
+ /* Inline icon: 1.25em (1.1em in a button), never squeezed in a flex row. */
11337
+ .oc-icon {
11338
+ inline-size: 1.25em;
11339
+ block-size: 1.25em;
11340
+ flex-shrink: 0;
11341
+ }
11342
+ .oc-btn > .oc-icon {
11343
+ inline-size: 1.1em;
11344
+ block-size: 1.1em;
11345
+ }
11346
+
11347
+ /* parts/site-brand-mark.css */
11348
+ /* File: packages/ui/src/parts/site-brand-mark.css */
11349
+ /* Map: brand mark (icon + text) -> word (accent part in the brand gradient) -> tagline -> sizes */
11350
+ /* A logo built from an icon and text: the mark (svg or img) with a soft glow in the brand color, the word mark with one
11351
+ part in the brand gradient, and a small tagline under it. Sizes --sm and --lg (a footer, a hero or a map). */
11352
+
11353
+ .oc-brand-mark {
11354
+ --oc-brand-mark-icon: 2.25rem;
11355
+ --oc-brand-mark-word: 1.35rem;
11356
+ --oc-brand-mark-tagline: 0.6rem;
11357
+ display: inline-flex;
11358
+ align-items: center;
11359
+ gap: 0.6rem;
11360
+ color: var(--oc-foreground);
11361
+ text-decoration: none;
11362
+ }
11363
+ .oc-brand-mark__icon {
11364
+ flex: none;
11365
+ inline-size: var(--oc-brand-mark-icon);
11366
+ block-size: var(--oc-brand-mark-icon);
11367
+ filter: drop-shadow(0 6px 14px oklch(from var(--oc-brand-1) l c h / 0.35));
11368
+ }
11369
+ .oc-brand-mark__text {
11370
+ display: grid;
11371
+ line-height: 1;
11372
+ }
11373
+ .oc-brand-mark__word {
11374
+ font-family: var(--oc-font-display, inherit);
11375
+ font-size: var(--oc-brand-mark-word);
11376
+ font-weight: 800;
11377
+ letter-spacing: -0.04em;
11378
+ }
11379
+ .oc-brand-mark__accent {
11380
+ background: linear-gradient(90deg, var(--oc-brand-1), var(--oc-brand-2));
11381
+ -webkit-background-clip: text;
11382
+ background-clip: text;
11383
+ color: transparent;
11384
+ }
11385
+ .oc-brand-mark__tagline {
11386
+ margin-block-start: 0.3rem;
11387
+ color: var(--oc-subtle-foreground, var(--oc-muted-foreground));
11388
+ font-family: var(--oc-font-mono);
11389
+ font-size: var(--oc-brand-mark-tagline);
11390
+ font-weight: 600;
11391
+ letter-spacing: 0.16em;
11392
+ text-transform: uppercase;
11393
+ }
11394
+ .oc-brand-mark--sm {
11395
+ --oc-brand-mark-icon: 1.75rem;
11396
+ --oc-brand-mark-word: 1.1rem;
11397
+ --oc-brand-mark-tagline: 0.55rem;
11398
+ }
11399
+ .oc-brand-mark--lg {
11400
+ --oc-brand-mark-icon: 2.6rem;
11401
+ --oc-brand-mark-word: 1.6rem;
11402
+ --oc-brand-mark-tagline: 0.66rem;
11403
+ }
11404
+
11405
+ /* parts/site-cards.css */
11406
+ /* File: packages/ui/src/parts/site-cards.css */
11407
+ /* Map: feature card -> link card -> icon list (+ points) -> accent list + note -> badge row + accent tagline
11408
+ -> screenshot grid -> browser frame glow + mark */
11409
+ /* Cards and lists for the pages of a company site: services and features (icon tile + title + text), cards that are
11410
+ links, a list of icon rows in frames (or as points with a title), a list with markers in the accent color, a small
11411
+ note, a row of badges, a tagline in the accent color, a grid of screenshots that open in the lightbox, and a browser
11412
+ frame with a glow in the accent color and a product mark on it. */
11413
+
11414
+ /* Feature / service card: tile, title, text; a quiet hover. In an .oc-card-grid (--oc-card-min sets the column width). */
11415
+ .oc-feature-card {
11416
+ padding: 1.6rem;
11417
+ border: 1px solid var(--oc-border);
11418
+ border-radius: var(--oc-radius-lg, 1.25rem);
11419
+ background: var(--oc-surface);
11420
+ transition: border-color 0.3s, background 0.3s;
11421
+ }
11422
+ .oc-feature-card:hover {
11423
+ border-color: var(--oc-border-strong);
11424
+ background: var(--oc-surface-strong);
11425
+ }
11426
+ .oc-feature-card > .oc-icon-tile { margin-block-end: 1.1rem; }
11427
+ .oc-feature-card__title {
11428
+ margin: 0 0 0.6rem;
11429
+ font-family: var(--oc-font-display, inherit);
11430
+ font-size: 1.2rem;
11431
+ line-height: 1.25;
11432
+ }
11433
+ .oc-feature-card__text {
11434
+ margin: 0;
11435
+ color: var(--oc-muted-foreground);
11436
+ font-size: 0.95rem;
11437
+ }
11438
+
11439
+ /* Card that is a link (an agent, a tool, a document): tile, title, text and a "more" line; hover in the accent color. */
11440
+ .oc-link-card {
11441
+ display: grid;
11442
+ align-content: start;
11443
+ gap: 0.4rem;
11444
+ padding: 1.25rem;
11445
+ border: 1px solid var(--oc-border);
11446
+ border-radius: var(--oc-radius-lg, 1.25rem);
11447
+ background: var(--oc-surface);
11448
+ color: var(--oc-foreground);
11449
+ text-decoration: none;
11450
+ transition: border-color 0.3s, background 0.3s;
11451
+ }
11452
+ /* --oc-link-card-hover: the hover color alone (without an accent preset, so the tile keeps its own color). */
11453
+ .oc-link-card:hover {
11454
+ border-color: oklch(from var(--oc-link-card-hover, var(--oc-accent-from)) l c h / 0.5);
11455
+ background: var(--oc-surface-strong);
11456
+ }
11457
+ .oc-link-card:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
11458
+ .oc-link-card > .oc-icon-tile { margin-block-end: 0.4rem; }
11459
+ .oc-link-card__title { font-family: var(--oc-font-display, inherit); font-weight: 700; }
11460
+ .oc-link-card__text { color: var(--oc-muted-foreground); font-size: 0.93rem; }
11461
+ .oc-link-card__more {
11462
+ display: inline-flex;
11463
+ align-items: center;
11464
+ gap: 0.35rem;
11465
+ margin-block-start: 0.4rem;
11466
+ font-size: 0.9rem;
11467
+ font-weight: 600;
11468
+ }
11469
+ .oc-link-card__more::after { content: "→"; transition: translate 0.25s var(--oc-ease-out, ease); }
11470
+ .oc-link-card:hover .oc-link-card__more::after { translate: 4px 0; }
11471
+
11472
+ /* Icon list: rows with an icon in a frame. --points: an icon tile next to a title and a text, without frames. */
11473
+ .oc-icon-list {
11474
+ display: grid;
11475
+ gap: 0.75rem;
11476
+ margin: 0;
11477
+ padding: 0;
11478
+ list-style: none;
11479
+ }
11480
+ .oc-icon-list > li {
11481
+ display: flex;
11482
+ align-items: center;
11483
+ gap: 0.75rem;
11484
+ padding: 0.85rem 1rem;
11485
+ border: 1px solid var(--oc-border);
11486
+ border-radius: var(--oc-radius-md, 0.75rem);
11487
+ background: var(--oc-surface);
11488
+ }
11489
+ .oc-icon-list > li > :is(svg, iconify-icon, .oc-icon) {
11490
+ flex: none;
11491
+ inline-size: 1.25rem;
11492
+ block-size: 1.25rem;
11493
+ font-size: 1.25rem;
11494
+ color: var(--oc-primary);
11495
+ }
11496
+ .oc-icon-list--points { gap: 1rem; }
11497
+ .oc-icon-list--points > li {
11498
+ display: grid;
11499
+ grid-template-columns: auto 1fr;
11500
+ align-items: start;
11501
+ gap: 0.25rem 0.9rem;
11502
+ padding: 0;
11503
+ border: 0;
11504
+ background: none;
11505
+ }
11506
+ .oc-icon-list--points > li > .oc-icon-tile { grid-row: span 2; }
11507
+ .oc-icon-list__title { font-family: var(--oc-font-display, inherit); font-weight: 700; }
11508
+ .oc-icon-list__text { color: var(--oc-muted-foreground); font-size: 0.95rem; }
11509
+
11510
+ /* List with markers in the accent color, and a small note under a block. */
11511
+ .oc-list--accent {
11512
+ display: grid;
11513
+ gap: 0.6rem;
11514
+ margin: 0;
11515
+ padding-inline-start: 1.1rem;
11516
+ list-style: disc;
11517
+ color: var(--oc-muted-foreground);
11518
+ font-size: 0.95rem;
11519
+ }
11520
+ .oc-list--accent > li::marker { color: var(--oc-accent-from, var(--oc-primary)); }
11521
+ .oc-note {
11522
+ margin: 1.25rem 0 0;
11523
+ color: var(--oc-muted-foreground);
11524
+ font-size: 0.85rem;
11525
+ }
11526
+ .oc-note--md {
11527
+ max-inline-size: 60rem;
11528
+ font-size: 1rem;
11529
+ }
11530
+
11531
+ /* Row of badges or pills above a title, and a tagline in the accent color (lighter, so it reads on a dark page). */
11532
+ .oc-badge-row {
11533
+ display: flex;
11534
+ flex-wrap: wrap;
11535
+ gap: 0.5rem;
11536
+ margin-block-end: 1.25rem;
11537
+ }
11538
+ .oc-tagline--accent {
11539
+ margin: 0 0 1rem;
11540
+ color: light-dark(var(--oc-accent-from), color-mix(in oklch, var(--oc-accent-from) 55%, white));
11541
+ font-size: clamp(1.2rem, 2.5vw, 1.6rem);
11542
+ font-weight: 600;
11543
+ }
11544
+
11545
+ /* Screenshot grid: figures with the whole image visible (contain, 16:10 on the card color) and a caption;
11546
+ a link or a button around the image opens the lightbox
11547
+ (<button command="show-modal" commandfor="<lightbox id>">). A <picture> (animated WebP with a still image) works the same. */
11548
+ .oc-shot-grid {
11549
+ display: grid;
11550
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--oc-shot-min, 18rem)), 1fr));
11551
+ gap: 1.25rem;
11552
+ }
11553
+ .oc-shot {
11554
+ margin: 0;
11555
+ border: 1px solid var(--oc-border);
11556
+ border-radius: var(--oc-radius-lg, 1.25rem);
11557
+ overflow: hidden;
11558
+ background: var(--oc-surface);
11559
+ }
11560
+ .oc-shot > :is(a, button) { display: block; inline-size: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; }
11561
+ .oc-shot > :is(a, button):focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; }
11562
+ .oc-shot :is(picture) { display: block; }
11563
+ .oc-shot img {
11564
+ display: block;
11565
+ inline-size: 100%;
11566
+ aspect-ratio: var(--oc-shot-ratio, 16 / 10);
11567
+ object-fit: contain;
11568
+ background: var(--oc-card);
11569
+ }
11570
+ .oc-shot > figcaption {
11571
+ padding: 0.8rem 1rem;
11572
+ color: var(--oc-muted-foreground);
11573
+ font-size: 0.88rem;
11574
+ }
11575
+
11576
+ /* Browser frame with a shadow in the accent color and a product mark on the bottom end corner. */
11577
+ .oc-browser-frame--glow {
11578
+ position: relative;
11579
+ box-shadow: 0 40px 90px -40px color-mix(in srgb, var(--oc-accent-from) 70%, transparent);
11580
+ }
11581
+ .oc-browser-frame--glow :is(.oc-browser-frame__view, .oc-browser-frame__body) img { transition: scale 0.6s var(--oc-ease-out, ease); }
11582
+ .oc-browser-frame--glow:hover :is(.oc-browser-frame__view, .oc-browser-frame__body) img { scale: 1.02; }
11583
+ .oc-browser-frame__mark {
11584
+ position: absolute;
11585
+ inset-block-end: 1rem;
11586
+ inset-inline-end: 1rem;
11587
+ filter: drop-shadow(0 10px 20px oklch(0 0 0 / 0.6));
11588
+ }
11589
+
11113
11590
  /* parts/site-fact-list.css */
11114
11591
  /* File: packages/ui/src/parts/site-fact-list.css
11115
11592
  Map: plain -> compact -> stacked -> accent values -> narrow screens
@@ -11137,6 +11614,48 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11137
11614
  /* Very narrow containers: wrap instead of squeezing label and value together. */
11138
11615
  .oc-fact-list > div { flex-wrap: wrap; }
11139
11616
 
11617
+ /* parts/site-fly.css */
11618
+ /* File: packages/ui/src/parts/site-fly.css */
11619
+ /* Map: fly in (only with fly.js) -> visible -> reduced motion -> product card badge -> code window tokens */
11620
+ /* Cards that fly in when they scroll into view: they start from the middle of their row (--oc-fly-x, set by fly.js),
11621
+ 70px lower, smaller and tilted, and land in their place. Without fly.js (html.oc-fly-ready is missing) or with
11622
+ "reduce motion" everything simply stands in its place. Options: --oc-fly-step (offset per column), --oc-fly-stagger
11623
+ (delay per column) and data-oc-fly-repeat (fly out again when leaving the view). The lift on hover uses translate, so it never fights the
11624
+ transform of the fly-in. Also: a "New" badge in a product card name and short syntax classes for a code window. */
11625
+
11626
+ .oc-fly-ready .oc-reveal--fly {
11627
+ /* --oc-fly-delay: set per card by fly.js from --oc-fly-stagger (delay per column). */
11628
+ transition:
11629
+ opacity 0.6s var(--oc-ease-out, ease) var(--oc-fly-delay, 0s),
11630
+ transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) var(--oc-fly-delay, 0s),
11631
+ border-color 0.35s;
11632
+ }
11633
+ .oc-fly-ready .oc-reveal--fly:not(.is-visible) {
11634
+ opacity: 0;
11635
+ transform: perspective(900px) translate3d(var(--oc-fly-x, 0), 70px, 0) scale(0.9) rotateX(14deg);
11636
+ }
11637
+ @media (prefers-reduced-motion: reduce) {
11638
+ .oc-fly-ready .oc-reveal--fly:not(.is-visible) { opacity: 1; transform: none; }
11639
+ }
11640
+
11641
+ /* "New" after the name of a product card: <h3 class="oc-product-card__name">Product 1 <span class="oc-product-card__badge oc-pill">New</span></h3> */
11642
+ .oc-product-card__badge {
11643
+ margin-inline-start: 0.4rem;
11644
+ vertical-align: middle;
11645
+ font-family: var(--oc-font-sans);
11646
+ font-size: 0.7rem;
11647
+ letter-spacing: 0.04em;
11648
+ text-transform: uppercase;
11649
+ }
11650
+
11651
+ /* Short syntax classes for hand-written code in an .oc-code-window: keyword, string, constant, function, muted.
11652
+ They take the colors of the window (--oc-syntax-*), so they follow light and dark mode and a theme can change them. */
11653
+ .oc-code-window .oc-tok-k { color: var(--oc-syntax-keyword); }
11654
+ .oc-code-window .oc-tok-s { color: var(--oc-syntax-string); }
11655
+ .oc-code-window .oc-tok-c { color: var(--oc-syntax-constant); }
11656
+ .oc-code-window .oc-tok-f { color: var(--oc-syntax-function); }
11657
+ .oc-code-window .oc-tok-m { color: var(--oc-syntax-comment); }
11658
+
11140
11659
  /* parts/site-header-variants.css */
11141
11660
  /* File: packages/ui/src/parts/site-header-variants.css
11142
11661
  Map: actions slot -> centred menu -> dark -> floating pill -> glass pill -> footer: centred, dark, newsletter, social
@@ -11209,6 +11728,77 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11209
11728
  .oc-site-footer__signup .oc-input { flex: 1 1 12rem; min-inline-size: 0; }
11210
11729
  .oc-site-footer--dark .oc-site-footer__signup .oc-input { color: var(--color-white-50); background: oklch(from var(--color-white-50) l c h / 0.08); border-color: oklch(from var(--color-white-50) l c h / 0.2); }
11211
11730
 
11731
+ /* parts/site-layout.css */
11732
+ /* File: packages/ui/src/parts/site-layout.css */
11733
+ /* Map: page hero -> section head / more -> action row -> group title -> band -> meta line */
11734
+ /* Page building blocks for a marketing or company site, on top of the showcase classes (.oc-section-pad, .oc-display):
11735
+ the larger header of an inner page, a section head and a "see more" row, a row of buttons, a group title with a count,
11736
+ a full-width band (for a stat strip) and a meta line (version · date). */
11737
+
11738
+ /* Inner page header: more room on top, less below, and a larger display title. */
11739
+ .oc-section-pad--page { padding-block: clamp(4rem, 10vw, 7rem) clamp(2rem, 5vw, 3rem); }
11740
+ .oc-display--page {
11741
+ margin-block-end: 1.25rem;
11742
+ font-size: clamp(2.5rem, 6.5vw, 4.75rem);
11743
+ }
11744
+
11745
+ /* Head of a section (eyebrow, title, lead) and the row with a "see all" link under the section. */
11746
+ .oc-section-head {
11747
+ max-inline-size: 46rem;
11748
+ margin-block-end: 3rem;
11749
+ }
11750
+ .oc-section-head--center {
11751
+ margin-inline: auto;
11752
+ text-align: center;
11753
+ }
11754
+ .oc-section-more { margin-block-start: 2.5rem; }
11755
+
11756
+ /* Row of buttons under a text: wraps on a narrow screen. */
11757
+ .oc-actions {
11758
+ display: flex;
11759
+ flex-wrap: wrap;
11760
+ gap: 0.75rem;
11761
+ margin-block-start: 2rem;
11762
+ }
11763
+ .oc-actions--center { justify-content: center; }
11764
+ .oc-actions--end { justify-content: flex-end; }
11765
+ .oc-actions--tight { margin-block-start: 1rem; }
11766
+
11767
+ /* Title of a group of cards with the number of items in a pill: <h3 class="oc-group-title">Apps <span class="oc-group-title__count">8</span></h3> */
11768
+ .oc-group-title {
11769
+ display: flex;
11770
+ align-items: center;
11771
+ gap: 0.75rem;
11772
+ margin: 0 0 1.5rem;
11773
+ font-size: 1.4rem;
11774
+ }
11775
+ .oc-group-title__count {
11776
+ padding: 0.1rem 0.6rem;
11777
+ border-radius: 999px;
11778
+ background: var(--oc-surface-strong);
11779
+ color: var(--oc-muted-foreground);
11780
+ font-size: 0.85rem;
11781
+ font-weight: 500;
11782
+ }
11783
+
11784
+ /* Band over the full width of the page with a line above and below; the content stays in its .oc-container.
11785
+ Put a .oc-stat-strip--plain in it for a row of key figures. */
11786
+ .oc-band {
11787
+ border-block: 1px solid var(--oc-border);
11788
+ background: var(--oc-band-bg, var(--oc-surface));
11789
+ }
11790
+
11791
+ /* Meta line under a page title: version · date, author. */
11792
+ .oc-meta {
11793
+ margin: 0 0 1rem;
11794
+ color: var(--oc-subtle-foreground, var(--oc-muted-foreground));
11795
+ font-size: 0.9rem;
11796
+ }
11797
+
11798
+ /* Muted and subtle text from the theme tokens, for running text inside a panel, a spotlight or a split. */
11799
+ .oc-text-muted { color: var(--oc-muted-foreground); }
11800
+ .oc-text-subtle { color: var(--oc-subtle-foreground, var(--oc-muted-foreground)); }
11801
+
11212
11802
  /* parts/site-link-arrow.css */
11213
11803
  /* File: packages/ui/src/parts/site-link-arrow.css
11214
11804
  Map: focus ring -> back arrow -> external arrow -> color modifiers -> sizes -> reduced motion
@@ -11414,6 +12004,37 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11414
12004
  .oc-next-teaser__tagline::before { transition: none; }
11415
12005
  }
11416
12006
 
12007
+ /* parts/site-panel.css */
12008
+ /* File: packages/ui/src/parts/site-panel.css */
12009
+ /* Map: panel -> glow (start / end / both) */
12010
+ /* A framed panel on the page (a form, a call to action, a feature with a list): card background, strong border, large
12011
+ radius, and room above it when a link jumps to it (#form). --glow adds a soft radial light in a corner:
12012
+ --oc-panel-glow-color (brand 1) and --oc-panel-glow-at (top left); --glow-end puts it top right, --glow-both adds a
12013
+ second light (brand 3) bottom right. */
12014
+
12015
+ .oc-panel {
12016
+ --oc-panel-glow-color: var(--oc-brand-1);
12017
+ --oc-panel-glow-at: 0% 0%;
12018
+ position: relative;
12019
+ padding: var(--oc-panel-padding, clamp(1.5rem, 4vw, 2.5rem));
12020
+ border: 1px solid var(--oc-border-strong);
12021
+ border-radius: calc(var(--oc-radius-lg, 1.25rem) * 1.2);
12022
+ background: var(--oc-card);
12023
+ scroll-margin-top: var(--oc-scroll-offset, 6rem);
12024
+ }
12025
+ .oc-panel--glow {
12026
+ background:
12027
+ radial-gradient(30rem circle at var(--oc-panel-glow-at), oklch(from var(--oc-panel-glow-color) l c h / 0.12), transparent 60%),
12028
+ var(--oc-card);
12029
+ }
12030
+ .oc-panel--glow-end { --oc-panel-glow-at: 100% 0%; }
12031
+ .oc-panel--glow-both {
12032
+ background:
12033
+ radial-gradient(40rem circle at 0% 0%, oklch(from var(--oc-brand-1) l c h / 0.18), transparent 55%),
12034
+ radial-gradient(40rem circle at 100% 100%, oklch(from var(--oc-brand-3) l c h / 0.2), transparent 55%),
12035
+ var(--oc-card);
12036
+ }
12037
+
11417
12038
  /* parts/site-price-card.css */
11418
12039
  /* File: packages/ui/src/parts/site-price-card.css
11419
12040
  Map: card -> head + discount badge -> period toggle -> monthly / yearly / lifetime price (:has) -> features -> featured
@@ -11833,6 +12454,18 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11833
12454
  @media (prefers-reduced-motion: reduce) {
11834
12455
  .oc-prose :where(a) { transition: none; }
11835
12456
  }
12457
+
12458
+ /* ---- Legal text (terms, privacy, cookies): narrower, muted body text in a slightly larger size, headings and strong
12459
+ in the foreground, disc lists with a gap, links in brand 1. A table in an .oc-table-wrap scrolls on a phone. ---- */
12460
+ .oc-prose--legal { max-inline-size: 46rem; color: var(--oc-muted-foreground); font-size: 1.0625rem; }
12461
+ .oc-prose--legal :where(h2) { margin-block-start: 2.5rem; font-size: 1.35rem; color: var(--oc-foreground); }
12462
+ .oc-prose--legal :where(h3) { margin-block-start: 1.5rem; font-size: 1.05rem; color: var(--oc-foreground); }
12463
+ .oc-prose--legal :where(strong, b) { color: var(--oc-foreground); }
12464
+ .oc-prose--legal :where(ul) { display: grid; gap: 0.4rem; padding-inline-start: 1.25rem; list-style: disc; }
12465
+ .oc-prose--legal :where(ul > li) { margin-block-start: 0; }
12466
+ .oc-prose--legal :where(a) { color: var(--oc-brand-1); font-weight: inherit; text-decoration-color: currentColor; text-underline-offset: 3px; }
12467
+ .oc-prose--legal :where(a:hover) { color: var(--oc-foreground); }
12468
+ .oc-prose--legal :where(table) { font-size: 0.93rem; }
11836
12469
 
11837
12470
  /* parts/site-stack-list.css */
11838
12471
  /* File: packages/ui/src/parts/site-stack-list.css
@@ -11940,6 +12573,52 @@ a.oc-message__quote:focus-visible { outline: 2px solid var(--oc-ring); outline-o
11940
12573
  /* Line in the accent color instead of the neutral border. */
11941
12574
  .oc-timeline--accent { border-inline-start-color: color-mix(in oklch, var(--oc-accent-from) 55%, transparent); }
11942
12575
 
12576
+ /* parts/utility-hover.css */
12577
+ /* File: packages/ui/src/parts/utility-hover.css */
12578
+ /* Map: smooth timing -> lift / grow / glow / none -> steady hover area -> reduced motion */
12579
+ /* Hover effect of a card, chosen with one class on the card (any card: .oc-card, product, feature, link, price, project …):
12580
+ .oc-hover-lift moves up (--oc-hover-lift, 6px) with a soft shadow; what a product card does by default
12581
+ .oc-hover-grow grows a little in place (--oc-hover-scale, 1.02)
12582
+ .oc-hover-glow stays in place, with a glow in the accent color
12583
+ .oc-hover-none no movement (also turns off the default lift of a product or price card)
12584
+ They share one smooth curve: --oc-hover-duration (0.45s) and --oc-hover-ease. This file loads after the cards, so the class wins. */
12585
+
12586
+ :is(.oc-hover-lift, .oc-hover-grow, .oc-hover-glow, .oc-product-card) {
12587
+ transition:
12588
+ translate var(--oc-hover-duration, 0.45s) var(--oc-hover-ease, cubic-bezier(0.22, 1, 0.36, 1)),
12589
+ scale var(--oc-hover-duration, 0.45s) var(--oc-hover-ease, cubic-bezier(0.22, 1, 0.36, 1)),
12590
+ box-shadow var(--oc-hover-duration, 0.45s) var(--oc-hover-ease, cubic-bezier(0.22, 1, 0.36, 1)),
12591
+ border-color 0.35s,
12592
+ background-color 0.35s,
12593
+ --oc-glow 0.35s;
12594
+ }
12595
+ .oc-product-card:hover { translate: 0 calc(-1 * var(--oc-hover-lift, 6px)); }
12596
+ .oc-hover-lift:hover {
12597
+ translate: 0 calc(-1 * var(--oc-hover-lift, 6px));
12598
+ box-shadow: 0 1.25rem 2.5rem -1.25rem oklch(from var(--color-black-60) l c h / 0.35);
12599
+ }
12600
+ .oc-hover-grow:hover { translate: none; scale: var(--oc-hover-scale, 1.02); }
12601
+ .oc-hover-glow:hover {
12602
+ translate: none;
12603
+ box-shadow:
12604
+ 0 0 0 1px oklch(from var(--oc-accent-from, var(--oc-primary)) l c h / 0.4),
12605
+ 0 0.75rem 2.5rem -0.75rem oklch(from var(--oc-accent-from, var(--oc-primary)) l c h / 0.5);
12606
+ }
12607
+ .oc-hover-none:hover { translate: none; scale: none; }
12608
+
12609
+ /* While a card moves up, the strip it leaves keeps the hover, so a mouse at the bottom edge does not make it bounce. */
12610
+ :where(.oc-hover-lift) { position: relative; }
12611
+ :is(.oc-hover-lift, .oc-product-card:not([class*="oc-hover-"])):hover::before {
12612
+ content: "";
12613
+ position: absolute;
12614
+ inset: 0 0 calc(-1 * var(--oc-hover-lift, 6px) - 2px);
12615
+ z-index: -1;
12616
+ }
12617
+
12618
+ @media (prefers-reduced-motion: reduce) {
12619
+ :is(.oc-hover-lift, .oc-hover-grow, .oc-product-card):hover { translate: none; scale: none; }
12620
+ }
12621
+
11943
12622
  /* File: packages/ui/src/code-block.css — Code-block component (line numbers + Omnia syntax theme) */
11944
12623
  /* Map: pre.code base -> CSS variables -> token spans (.hl-tag, .hl-attr, .hl-string, etc.) */
11945
12624
  /*