ART-378 · Odds CTA · V1 / V2 / V3 — scroll for the full spec ↓

Odds CTA

ART-378. Three variants, one set of betting options, one set of interaction states.

V1 — standalone. No bookmaker is named: a single light 36px horizontal block, 100px wide for every selection, with the label and odd centred side by side. For editorial / neutral contexts.
V2 — sponsored and V3 — best odds share a two-part card: a white top block holding the selection label and the odd side by side in dark text, and beneath it the bookmaker's rectangle logo tile at a fixed 26px height — the same height as the odds block above it. V2 repeats one operator's tile on every card; V3 shows whichever operator has the best odd per selection, so the tiles vary. This is what ships on foxbet.gr/analysis.

All three carry the same markets — 1X2 · O 2.5/U 2.5 · Ακριβές σκορ on desktop, with a G/G slide added on mobile — and the same states, so hover behaves identically in all three. Each is shown at mobile 360px and desktop ≥1200px. UI strings are Greek exactly as they ship; the spec is English.

V1 — Standalone, no bookmaker

Editorial / neutral contexts. No tile, no responsible-gaming line. Uniform 100px cards, no flex-grow, with the label and odd centred with a 5px gap. Hover to see the tint enter from the top.

data-variant="v1"
Mobile · 360px · carousel slides Slide 1 — 1X2 Slide 2 — Over / Under Slide 3 — Ακριβές σκορ Slide 4 — G/G (mobile only)
Desktop ≥1200px · all three markets with dividers

V2 — Sponsored by one bookmaker

One operator sponsors the whole block, so every card carries the same tile. Same betting options as V1 — 1X2 · O 2.5/U 2.5 · Ακριβές σκορ on desktop, plus a G/G slide on mobile. Structurally identical to V3; only the data differs.

data-variant="v2"
Mobile · 360px · carousel slides
Desktop ≥1200px · all three markets with dividers Repeating one tile eight times reads as sponsorship, not comparison — that is the intent

V3 — Best odds, bookmaker varies per selection

Each selection is priced by whichever operator offers the best number, so the tile changes card to card. This is the configuration on /analysis. Same betting options and same markup as V2 — only the tile source and the odd differ.

data-variant="v3"
Mobile · 360px · carousel slides
Desktop ≥1200px · all three markets with dividers Mixed tiles read as comparison — the visual difference from V2 is entirely in the data

All remaining markets in this display

Extending V2/V3 beyond the three markets shipped today. The card form never changes — only the label, and therefore the card width. Decided: width is a property of the market, and every card inside one market carries the same width. A slide is therefore always uniform, and a desktop row only ever changes width at a .modd-div. One market per carousel slide on mobile; on desktop, ship the markets that fit the column and leave the rest to the carousel.

decided · one width per market
Base 72px · 1–4 char labels — one market per group 1X2 · Διπλή ευκαιρία · Ακριβές σκορ · Μονά/Ζυγά — four markets, all at the base width, so the row never steps
.is-w 92px · 5–8 char labels — one market per group Every Over/Under line · G/G — the market's number stays in the label, never in the odd slot
.is-xw 112px · 9–14 char labels — one market per group Ημίχρονο/Τελικό · Χάντικαπ · Σύνολο γκολ
Width is a property of the market — decided. Every card inside one market gets the same width class, so a mobile slide is always uniform and a desktop row only ever steps at a .modd-div. That keeps the ragged look out without forcing 1 / X / 2 up to 112px. Pick the class from the longest label in that market, then apply it to every card in the market — never per card.
Height is constant, only width changes. Every card above is the same total height — 26px top block + 26px tile — whichever width step it uses. The logo sits centred at its natural size and the brand colour fills the leftover width, so a 112px card and a 72px card have identical top and bottom edges. This is the rule that lets widths vary at all.
Labels that will not fit at any width — first scorer, scorecast, exact minute, player props. These carry a name rather than a code, so they need a different component (a stacked list row), not a wider tile card. Recommend keeping them out of this CTA entirely.

States — V1, V2 & V3

All three variants share one set of interaction states. Hover / focus / pressed are live — hover any card and tab into it. V2 and V3 hover exactly like V1: the white part shifts to the same white → #FFEBE6 gradient with the same soft neutral shadow. The border is never recoloured and the card is never translated. The logo tile is never tinted — the operator's colour must stay true in every state.

6 states × 3 variants
V1 — standalone, no bookmaker
Default
Hover
Focus-visible
Pressed
Loading
Suspended
V2 — sponsored · same tile on every card
Default
Focus-visible
Pressed
Loading
Suspended
V3 — best odds · tile varies per selection
Default
Focus-visible
Pressed
Loading
Suspended
One hover rule, three variants. The gradient is applied to .modd-card — the white part — in all three, so the affordance is identical whether or not a tile is present. In V1 the card is the whole component; in V2/V3 it is the top half and the shadow sits on the shell so it reads around the tile as well.
Suspended is not click-proof. The class handles appearance only — an <a href> stays keyboard-activatable regardless of CSS, so the click handler must return early for [aria-disabled="true"].

Design → Dev Handover

Odds CTA · Jira ART-378 · Foxbet design system. Approved — build as specified. Ship into foxbet-responsive.html and foxbet-responsive-live.html.
Three variants: V1 standalone, V2 sponsored, V3 best odds. V2 and V3 are the same card with different data; V1 is a simpler card with no bookmaker.

Assets που χρησιμοποιούνται

Τα tiles των bookmakers ζουν στον φάκελο logo-assets/rectangle/ — κάντε κλικ σε οποιοδήποτε για να το κατεβάσετε. Μόνο ο τύπος rectangle χρησιμοποιείται σε αυτό το component.

01Bookmaker tile assets

Every operator tile the component can render. The V2 and V3 variants (§02) place one of these beneath the odd; V1 uses none. Only the rectangle type is used here. Logos are exported from Figma page «🧩 Logos» → bookmakers-logos into logo-assets/. Click any asset name to open the SVG. Colours are listed so the .bk-* class can be verified against the artwork.

OperatorTileAssetClassBackground
Stoiximan rectangle/stoiximan.svg bk-stoiximan #0055FF
bet365 rectangle/bet365.svg bk-bet365 #005440
Fonbet rectangle/fonbet.svg bk-fonbet #D42B28
Betsson rectangle/betsson.svg bk-betsson #F26722
Interwetten rectangle/interwetten.svg bk-interwetten #FFD200
Sportingbet rectangle/sportingbet.svg bk-sportingbet #1E87C9
Vistabet rectangle/vistabet.svg bk-vistabet #F05722
Novibet rectangle/novibet.svg bk-novibet #303540 charcoal, not the DS teal — follow the artwork
NetBet rectangle/netbet.svg bk-netbet #333743
N1 Casino rectangle/n1casino.svg bk-n1casino #212121
bwin rectangle/bwin.svg bk-bwin #000000
Elabet rectangle/elabet.svg bk-elabet #11143E → #172B89 gradient.bk-elabet sets background directly, not --bk
Pamestoixima (long) rectangle/pamestoixima-long.svg bk-pamestoixima-long is-tall #B92128 — mark fills the tile, so it scales rather than crops (§03)
Pamestoixima rectangle/pamestoixima.svg modd-bk is-lt #FFFFFF
Winmasters rectangle/winmasters.svg modd-bk is-lt #FFFFFF
Winmasters 2 rectangle/winmasters-2.svg modd-bk is-lt #FFFFFF

The full library — all five types per brand (rectangle, circle-large, circle-small, plain-dark, plain-light) — is in logo-assets/. This component uses rectangle only.

White tiles need .is-lt. winmasters, winmasters-2 and pamestoixima sit on a white background, which would bleed into the white block above. .is-lt sets --bk:#FFFFFF and insets the glyph by 2px so the border-top reads as a real edge.
Adding an operator: export type=Rectangle as SVG, strip the #EFEFEF canvas rect and the #9747FF component-set boundary rect Figma adds to every export, drop it into logo-assets/rectangle/, then add one line to the .bk-* block with the colour sampled from the artwork's own background rect. The class name must match the filename exactly: bk-stoiximanrectangle/stoiximan.svg.
Alt text is the operator name only (alt="Stoiximan") — never "logo" and never the odd value. The tile is the only thing naming the operator, so it is not decorative.

02Variant map

One component, three variants. data-variant sits on .moddgrid for tracking and compliance — no CSS branches on it.

VariantAttributeCardBookmaker tile21+ lineUse for
V1 Standalonedata-variant="v1" Single light block, 36px tall, 100px wide NoneNo Editorial / neutral odds inside analysis copy
V2 Sponsoreddata-variant="v2" Two-part card — white label+odd block over the tile (§03) The same operator on every cardYes Sponsored blocks tied to a single operator
V3 Best oddsdata-variant="v3" The best-odds operator per selection — differs card to cardYes Odds comparison · this is the configuration for /analysis
V2 and V3 are markup-identical. Repeat one tile source for V2, vary it per selection for V3. One template and one stylesheet serve both.
All three share the same betting options and the same interaction states (§05, §06), so a user meets one behaviour across the whole page.

03V2 / V3 — the two-part card

Each selection is one link containing two stacked parts: a white top block with the selection label and the odd side by side, and beneath it the operator's rectangle logo tile.

The tile height never changes — only the card's width. The tile is a flat 26px at every width. Do not use aspect-ratio: a ratio makes the tile grow taller as the card widens, which breaks the row's baseline and makes «O 2.5» taller than «1». The logo still renders at 36px and is cropped by the band, so it keeps its size, and the brand colour fills whatever width is left.
The brand colour lives in two places and they must agree. Each tile SVG bakes its own background in, but the asset is a fixed 2:1 shape that cannot stretch — so on any card wider than 72px the colour must be continued in CSS. That is --bk, set by a .bk-<operator> class on the tile. The class must match the asset, or a seam appears at the logo's edge. Take the values from §01, never by eye.
PartPropertyValue
Shell
.modd
Width72px base · .is-w 92px · .is-xw 112px (§05)
Radius / clip6px all four corners + overflow:hidden — this is what rounds the tile
Border1px #E6E6E6 Base 200
Background#FFFFFF
Top block
.modd-card
Height26px fixed — not content-driven
Layoutflex-direction:row, centred, gap:5px, padding:0 6px
Selection label.modd-lbl — Roboto 10px / 600, #1A1A1A Base 950, no uppercase, no letter-spacing
Long-label step-down.modd-lbl.cs — Roboto 9px / 600, letter-spacing:-.1px
Odd.od — Roboto Mono 15px / 700, #1A1A1A, letter-spacing:0. The odd is the hero — 1.5× the label's size.
Logo tile
.modd-bk
Heightheight:26px; flex:0 0 26px — the same as the block above, fixed, identical at every width
Widthwidth:100% — follows the card: 72 / 92 / 112px
Logoheight:36px; width:auto; object-fit:contain, centred by display:flex; align-items:center; justify-content:center on the tile. Deliberately taller than the band — see the crop note below. The glyph renders at the same size whatever the card width; it does not scale with the card.
Cropoverflow:hidden on the tile removes 5px top and 5px bottom of the asset — its own brand-coloured padding, never the wordmark
Fillbackground:var(--bk) — continues the asset's own background across the leftover width. Set by .bk-<operator>.
Separatorborder-top:1px #E6E6E6
Assetlogo-assets/rectangle/<operator>.svg

Total card height is always 26 + 26 — two equal halves — so cards of different widths line up top and bottom. That is the rule that lets widths vary at all.

The band shrank; the logo did not. The tile is 26px but the <img> is still height:36px, so the wordmark is exactly the size it was at the taller band — the band just crops 5px off the top and bottom of the asset. Every rectangle export centres its wordmark inside generous brand-coloured padding, and --bk continues that same colour, so what gets cropped is invisible. Do not "fix" this by setting the logo to height:100% — that scales the wordmark down by 28%.
One asset cannot be cropped. Measured across all 16 exports, the wordmark ink occupies the middle 21–57% of the tile — comfortably inside the 72% that survives a 26/36 crop — with two borderline cases (interwetten and pamestoixima at 13.3% padding versus the 13.9% needed, so ~0.2px, sub-pixel and invisible). pamestoixima-long is the exception: its wordmark fills 87.6% of the tile with only 1.5% padding at the top, so a crop would cut 4.5px into the glyph. It carries .is-tall, which scales it to the 26px band instead. Any future export whose mark fills more than ~72% of its tile needs the same class.

04V1 — the standalone card

No bookmaker is named, so there is no tile: one light block holding the selection and the odd. Neutral editorial contexts.

ElementClassSpecToken
Grid.moddgrid display:flex · align-items:stretch · gap:8px · overflow-x:auto
Click target.modd flex:0 0 auto · width:100px — fixed and uniform for every market
Card.modd-card height:36px · justify-content:center · gap:5px · padding:0 6px · radius 8px · overflow:hidden --fill / --line
Label.modd-lbl inside .modd-sel Roboto 12px / 500--ink
Odd.od Roboto Mono 14px / 700, letter-spacing:.2px --acc #CB2F01
Uniform 100px, content centred. Fixed width means a 2-card market does not stretch its cards to fill the row while a 3-card market stays narrow. Centring with a 5px gap keeps the label and the odd reading as one unit rather than two things at opposite edges. 100px is the floor set by the longest label in the set: 100 − 12 padding − 5 gap − 34 odd = 49px of label room, which fits G/G Όχι.
The odd is #CB2F01, not #FD3A01. Primary 500 on #F2F2F2 is ~3.3:1 and fails AA at 14px. Primary 600 gives 4.76:1 and is what the design system designates for brand orange as text on light. In V1 the odd is focal through colour, which is what lets the card hold 36px with both texts small.

05Markets & widths

All three variants carry the same betting options.

MarketLabelsCardsV1 widthV2/V3 width
1X21 · X · 23 100px — uniform for every market base 72px
Over / UnderO 2.5 · U 2.52 .is-w 92px
Ακριβές σκορ1-0 · 2-12 base 72px
G/G (mobile only)G/G Ναι · G/G Όχι2 .is-w 92px
Mobile <1200pxDesktop ≥1200px
Layout Scroll-snap carousel, one market per view.modd-slide{flex:0 0 100%;scroll-snap-align:center}, grid gap:0, slide gap:6px Single centred row — .modd-slide{display:contents} so cards flow straight into the flex row, grid gap:6px
Markets 4 slides — 1X2 · O 2.5/U 2.5 · Ακριβές σκορ · G/G 3 markets, 7 cards + 2 dividers. G/G carries .only-m and is hidden; the row is already full.
Dividers None — each market is its own slide .modd-div1px #E6E6E6 between markets, margin:6px 4px, align-self:stretch
Affordance .modd-swipe chevron pinned right, 20×20, rgba(30,42,58,.5), 2.2s modd-nudge pulse; tap advances one market; gains .end (fades out) on the last slide Not rendered
Wrapper .oddswrap{width:100%;position:relative} .oddswrap{width:fit-content;margin:0 auto}
  • Width is a property of the market, not the card. Every card inside one market carries the same width class, picked from that market's longest label. A mobile slide is therefore always uniform, and a desktop row only ever steps width at a .modd-div.
  • Correct-score labels are 1-0 / 2-1. The market group is labelled above the row, so the prefix is redundant. The full string goes in title / aria-label, so nothing is lost for screen readers.
  • Use the full O 2.5 / U 2.5, never bare O / U.
  • Desktop minimum width: V1 8 × 100 + 7 × 8 gaps + 2 × 9 dividers = 874px. V2/V3 (5 × 72 + 2 × 92) + 7 × 6 + 2 × 9 = 604px. Both fit the analysis column; a narrower container scrolls horizontally rather than compressing.
  • An ellipsis on a label means the container is too narrow or the wrong width class was picked. Treat it as a bug, not a state.
  • Markets beyond these three follow the same rules — see the market-coverage stage above for the 72 / 92 / 112px sets. Markets carrying a name rather than a code (first scorer, scorecast, exact minute, player props) do not fit at any width and need a stacked list row instead; keep them out of this component.

06States

All three variants share one set of states, so hover behaves identically whether or not a tile is present.

StateHookVisualBehaviour & a11y
Default V1 --fill card · V2/V3 white block over the tile, --line border, no shadow
Hover:hover in @media (hover:hover) Internal gradient onlylinear-gradient(180deg,#FFEBE6 0%,#FFFFFF 100%) on .modd-card, plus 0 2px 8px rgba(26,26,26,.08) (on the shell for V2/V3). Border unchanged, no transform. The tile is never tinted. Hover-query guarded so touch devices never get a stuck hover
Focus:focus-visible 2px DS ring only — 0 0 0 2px rgba(253,58,1,.40). Border unchanged The only Primary 500 in any state
Pressed:active scale(.97) on the whole card
Loading.is-loading Shimmer over the odd only; label and tile stay readable aria-busy="true", pointer-events:none; animation off under prefers-reduced-motion
Suspended.is-susp Odd → , top block to 55% opacity, dashed border on the shell, tile greyscaled so the operator stays identifiable, hover suppressed aria-disabled="true" + title="Η αγορά δεν είναι διαθέσιμη". Block the click in JS
The hover tint starts at the TOP. The gradient runs #FFEBE6 → #FFFFFF downward, so the colour lands where the eye already is. This matters most on V2/V3, where the white part is only 26px tall.
Two things the hover must never do, because both render a visibly broken edge: (1) recolour the border — a translucent orange over a white fill composites to rgb(254,166,141), about 1.6:1 against white, so it reads as a missing line; (2) transform:translateY(-1px) — shifting by a fractional device pixel makes the 1px border anti-alias unevenly, top and bottom edges worst. Gradient plus shadow avoids both failure modes.
Suspended is not click-proof. The class handles appearance only — an <a href> stays keyboard-activatable regardless of CSS, so the click handler must return early for [aria-disabled="true"].

07Design tokens

UseValueDS nameWhere
Primary text · odd (V2/V3) #1A1A1A Base 950--ink
Odd (V1) #CB2F01 Primary 600--acc — AA-safe as text on light
Secondary text · 21+ line #666666 Base 700--mut
Borders · dividers #E6E6E6 Base 200--line
V1 card fill #F2F2F2 Base 100--fill
Hover tint #FFEBE6 Primary 50Gradient start
Focus ring rgba(253,58,1,.40) Primary 500Focus only
Radius6px V2/V3 shell · 8px V1 cardradius default
FontsRoboto · Roboto Mono --body / --numLabel · odd

08Responsible gaming & regulatory

  • V2 / V3 — render <span class="modd-rg">21+ · ΠΑΙΞΕ ΥΠΕΥΘΥΝΑ</span> once below the grid; 10px/600 uppercase in --mut. Required — both name an operator.
  • V1 — no inline line. No bookmaker is named, so the block is neutral editorial content.
  • The full ΕΕΕΠ row stays in the page footer as shipped — see handover-footer-eeep.html. It is not duplicated inside the component.
  • If the component is ever syndicated outside a page carrying the ΕΕΕΠ footer, the inline line becomes mandatory for V1 too.

09Accessibility

CheckResult
V2/V3 label & odd — #1A1A1A on #FFFFFF18.1:1 — AAA ✔
V2/V3 on hover, gradient top — #1A1A1A on #FFEBE615.2:1 — AAA ✔
V1 odd — #CB2F01 on #F2F2F24.76:1 — AA ✔
V1 odd on hover, gradient top — #CB2F01 on #FFEBE6 4.64:1 — AA ✔ (worst point of the gradient, so hover passes throughout)
V1 label — #1A1A1A on #F2F2F215.6:1 — AAA ✔
21+ line — #666666 on #FFFFFF5.7:1 — AA ✔
Hover is not colour-onlyThe fill changes from flat to a gradient and gains a shadow, so the state is a surface change, not a hue swap. Focus additionally carries the 2px ring
Operator is not colour-onlyThe tile carries the operator's wordmark, and the alt names it in text. Colour alone never conveys which bookmaker it is
Brand wordmarksLogos are exempt from text-contrast rules; each uses the brand's own approved colour on its own approved background
Reduced motionShimmer, card transitions and the swipe-chevron pulse are disabled under prefers-reduced-motion
Link labelEvery .modd needs title / aria-label naming the selection and, for V2/V3, the operator — e.g. «Ισοπαλία — καλύτερη απόδοση: bet365». "Best odds by X" is otherwise visual-only
Touch targetV2/V3 cards are 53px tall overall (26 + 26 + border) and the whole .modd is clickable. V1 is 36px. V1 sits under the 44px guideline — consistent with the DS's deliberate 33px mobile-button decision. Needs your confirmation.

10Markup — copy/paste

<!-- V2 / V3 — v2 repeats one operator, v3 varies it per selection -->
<div class="oddswrap">
  <div class="moddgrid" data-variant="v3">

    <!-- market 1: 1X2 — base 72px, 1-char labels -->
    <div class="modd-slide">
      <a class="modd" href="{deeplink}" target="_blank" rel="noopener"
         title="Ολλανδία — καλύτερη απόδοση: Stoiximan">
        <span class="modd-card">
          <span class="modd-lbl">1</span><span class="od">2.00</span>
        </span>
        <!-- .bk-* MUST match the src; add .is-lt for white-bg tiles -->
        <span class="modd-bk bk-stoiximan">
          <img src="logo-assets/rectangle/stoiximan.svg" alt="Stoiximan">
        </span>
      </a>
      <!-- X and 2 identical, each with its own operator tile -->
    </div>

    <span class="modd-div" aria-hidden="true"></span>

    <!-- market 2: O 2.5 / U 2.5 — .is-w (92px), 5-char label -->
    <div class="modd-slide">
      <a class="modd is-w" href="{deeplink}" title="Over 2.5 — …">
        <span class="modd-card">
          <span class="modd-lbl">O 2.5</span><span class="od">1.85</span>
        </span>
        <span class="modd-bk bk-bet365"> … </span>
      </a>
    </div>

    <span class="modd-div" aria-hidden="true"></span>

    <!-- market 3: Ακριβές σκορ — base 72px -->
    <div class="modd-slide"> … </div>

    <!-- market 4: G/G — .is-w, mobile carousel only -->
    <div class="modd-slide only-m"> … </div>
  </div>
  <button class="modd-swipe" type="button" aria-label="Επόμενη αγορά">›</button>
</div>
<span class="modd-rg">21+ · ΠΑΙΞΕ ΥΠΕΥΘΥΝΑ</span>

<!-- V1 — no tile, no 21+ line, uniform 100px cards -->
<div class="moddgrid" data-variant="v1">
  <a class="modd" href="{deeplink}" title="Ολλανδία">
    <span class="modd-card">
      <span class="modd-sel"><span class="modd-lbl">1</span></span>
      <span class="od">2.00</span>
    </span>
  </a>
</div>

<!-- loading -->   <a class="modd is-loading" aria-busy="true" …>
<!-- suspended --> <a class="modd is-susp" aria-disabled="true"
                  title="Η αγορά δεν είναι διαθέσιμη" …> <!-- odd renders as — -->

11Build checklist

  • Markup per selection (V2/V3): .modd.modd-card (.modd-lbl + .od) then .modd-bk<img>. The tile is the anchor's second child, a sibling of the top block — not inside it.
  • Point each <img src> at logo-assets/rectangle/<operator>.svg, give it the operator name as alt, and put the matching .bk-<operator> on the same .modd-bk. Add .is-lt for the white-background tiles.
  • V3: feed the tile from the best-odds field per selection, so a row mixes operators. V2: feed the same sponsor to every card. Same markup either way.
  • Keep the tile at a hard height:26px — never aspect-ratio. Widening a card must not make its tile taller.
  • Keep the logo at height:36px inside that 26px band — the overflow crop is intentional and is what keeps the wordmark full-size. Setting it to height:100% shrinks every logo by 28%. Add .is-tall only for assets whose mark fills more than ~72% of the tile (currently just pamestoixima-long).
  • Keep overflow:hidden on .modd, or the tile's square corners punch through the 6px radius.
  • Pick the width class from the market's longest label and apply it to every card in that market: base 72px for 1 X 2 1-0 2-1, .is-w 92px for O 2.5 U 2.5 G/G Ναι G/G Όχι.
  • Group each market in a .modd-slide; tag G/G .only-m; put a .modd-div between markets on desktop. Verify scroll-snap and the .modd-swipe chevron at 360px.
  • Hover must be identical in all three variants: gradient on .modd-card from the top, shadow on the shell, border untouched, no transform, tile never tinted.
  • Render the 21+ · ΠΑΙΞΕ ΥΠΕΥΘΥΝΑ line (.modd-rg) for V2 and V3.
  • Wire the suspended-state click guard on [aria-disabled="true"].
  • Mirror the identical change into foxbet-responsive-live.html — the odds CTA is the same in both; the only difference on the live page is the countdown versus the live score/clock elsewhere in the banner.
One item needs your call: V1's 36px card against the 44px touch-target guideline (§09).